rust 测试 指定_测试与类型— Rust版本

rust 测试 指定

A few days ago 0xd34df00d has published the translation of the article, describing the possible information about some function if we use it as a "black box", not trying to read its implementation. Of course, this information is quite different from language to language; in the original article, four cases were considered:

几天前, 0xd34df00d发布了本文的译文,描述了如果我们将某些功能用作“黑匣子”,而不是尝试阅读其实现的信息,那么有关该功能的可能信息。 当然,这些信息因语言而异。 在原始文章中,考虑了四种情况:

  • Python — dynamic typing, almost no information from signature, some hints are gained by the tests;

    Python-动态类型,几乎没有来自签名的信息,测试获得了一些提示;
  • C — weak static typing, a little more information;

    C-弱静态键入,更多信息;
  • Haskell — strong static typing, with pure functions by default, a lot more information;

    Haskell —强静态类型,默认情况下具有纯函数,提供更多信息;
  • Idris — dependent typing, compiler can prove the function correctness.

    Idris-依赖类型,编译器可以证明函数的正确性。

"Here's C and there's Haskell, and what about Rust?" — this was the first question in the following discussion. The reply is here.

“这里是C,还有Haskell,Rust呢?” -这是以下讨论中的第一个问题。 答复在这里。

Let's first recall the task:

让我们首先回顾一下任务:

Given a list of values and a value, return the index of the value in the list or signify that it is not present in the list.
给定一个值列表和一个值,请返回该值在列表中的索引或表示该值不存在于列表中。

If someone don't want don't want to read this all, the code examples are provided in the Rust playground. Otherwise, let's start!

如果不想让所有人都读完这本书, Rust操场上提供了代码示例。 否则,让我们开始吧!

The first approach will be the almost naive signature, differing from the C code only in some idiomatic elements:

第一种方法将是几乎天真的签名,仅在某些惯用元素上与C代码不同:

fn foo(x: &[i32], y: i32) -> Option<usize> {
    // Implementation elided 
}

What do we know about this function? Well, in fact — not very much. Of course, Option<usize> as a return value is a great improvement over whatever is provided by C, but there's no information about the function semantics. In particular, we have no guarantee of the side-effects being absent and no way to somehow check the desired behaviour.

我们对该功能了解多少? 好吧,事实上-不是很多。 当然, Option<usize>作为返回值是对C所提供内容的极大改进,但是没有有关函数语义的信息。 特别是,我们无法保证不存在任何副作用,也无法以某种方式检查所需的行为。

Can a test improve this? Look here:

测试可以改善吗? 看这里:

#[test]
fn test() {
    assert_eq!(foo(&[1, 2, 3], 2), Some(1));
    assert_eq!(foo(&[1, 2, 3], 4), None);
}

Nothing more, it seems — all these checks can be just the same in Python (and, in anticipation, tests will be of little help for the whole article).

似乎仅此而已-所有这些检查在Python中可能都是一样的(而且,预期测试对整篇文章没有多大帮助)。

使用通用名称,卢克! (Use the generics, Luke!)

But is it any good that we have to use only 32-bit signed numbers? Fixing it:

但是仅使用32位带符号的数字有什么好处? 解决方法:

fn foo<El>(x: &[El], y: El) -> Option<usize>
where
    El: PartialEq,
{
    // Implementation elided 
}

Well, that's something! Now we can take any slice, consisting of elements of any comparable type. Explicit polymorphism is almost always better that implicit (hello, Python) and almost always better then no polymorphism at all (hello, C), right?

好吧,那是什么! 现在我们可以进行任何切片,包括任何可比较类型的元素。 显式多态几乎总是比隐式(hello,Python)好,根本没有多态(hello,C)。

Although, this function might unexpectedly pass this test:

虽然,此函数可能会意外通过此测试:

fn refl<El: PartialEq + Copy>(el: El) -> Option<usize> {
    foo(&[el], el) // should always return Some(0), right?
}
#[test]
fn dont_find_nan() {
    assert_eq!(refl(std::f64::NAN), None);
}

This hints on the one missing point, since the specification wants the refl function, in fact, to always return Some(0). Of course, this is all due to the specific behaviour of the partially-equivalent types in general and floats in particular. Maybe we want to get rid of this problem? So, we'll simply tighten the bound on the El type:

这暗示了一个缺失点,因为规范实际上希望refl函数始终返回Some(0) 。 当然,这全都归因于部分等效类型的一般行为,尤其是浮点数。 也许我们想摆脱这个问题? 因此,我们将简单地限制El类型的界限:

fn foo<El>(x: &[El], y: El) -> Option<usize>
where
    El: Eq,
{
    // Implementation elided 
}

Now, we're requiring not only the type to be comparable, — we require this comparisons to be equivalences. This, of course, limits the possible types to use with this function, but now both the signature and the tests hint that the behaviour should fit into the specification.

现在,我们不仅要求类型具有可比性,还要求这种比较是等价的 。 当然,这限制了此功能可以使用的类型,但是现在签名和测试都暗示行为应符合规范。

旁注:我们想更通用! (Side note: we want to go MORE generic!)

This case has no relation to the initial task, but this seems to be a good example of the well-known principle: "be liberal in what you accept, be conservative in what you do". In other words: if you can generalize the input type without harming the ergonomics and performance — you probably should do it.

这个案例与最初的任务无关,但这似乎是众所周知的原则的一个很好的例子:“对接受的东西持开放态度,对所做的事情持保守态度”。 换句话说:如果您可以在不损害人体工程学和性能的情况下概括输入类型,则可能应该这样做。

Now, we'll check this:

现在,我们将检查以下内容:

fn foo<'a, El: 'a>(x: impl IntoIterator<Item = &'a El>, y: El) -> Option<usize>
where
    El: Eq,
{
    // Implementation elided 
}

What do we know about this function now? In general, all the same, but now it accepts not only the slice or list, but some arbitrary object, which can yield the references to the type El, so that we compare it with the object in question. For example, if I'm not mistaken, in Java this type would be Iterable<Comparable>.

现在我们对该功能有什么了解? 总的来说,它们都是一样的,但是现在它不仅接受切片或列表,还接受一些任意对象,这些对象可以产生对类型El的引用,以便我们将其与所讨论的对象进行比较。 例如,如果我没记错的话,在Java中,此类型为Iterable<Comparable>

像以前一样,但是更加严格 (Like before, but a bit more strict)

But now, maybe we need some more guaranties. Or we want to work on the stack (and so can't use Vec), but need to generalize our code for every possible array size. Or we want to compile the function optimized for every concrete array size.

但是现在,也许我们需要更多保证。 或者我们想在堆栈上工作(因此不能使用Vec ),但是需要针对每种可能的数组大小归纳我们的代码。 或者我们要编译针对每种具体数组大小优化的函数。

Anyway, we need a generic array — and there's a crate in Rust, providing exactly that.

无论如何,我们需要一个通用数组-Rust中有一个板条箱, 正是提供了这一点

Now, here's our code:

现在,这是我们的代码:

use generic_array::{GenericArray, ArrayLength};

fn foo<El, Size>(x: GenericArray<El, Size>, y: El) -> Option<usize>
where
    El: Eq,
    Size: ArrayLength<El>,
{
    // Implementation elided 
}

What do we know from it? We know that function will take the array of some particular size, reflected in its type (and will be compiled independently for each such size). For now, this is almost nothing — the same guarantees were provided at runtime by the previous implementation.

我们从中知道什么? 我们知道函数将采用某个特定大小的数组,并在其类型中反映出来(并且将针对每个此类大小独立编译)。 目前,这几乎没有什么-以前的实现在运行时提供了相同的保证。

But we can get further.

但是我们可以走得更远。

类型级算术 (Type-level arithmetic)

The initial article mentioned several guarantees provided by Idris which were impossible to get from other languages. One of them — and probably the simplest, since it doesn't involve any proofs or tests, just a little change in types, — states that the return value, if it's not Nothing, will always be less then the list length.

最初的文章提到了Idris提供的一些保证,这些保证是其他语言无法实现的。 其中之一-可能是最简单的,因为它不涉及任何证明或测试,只是类型略有变化-指出返回值(如果不是Nothing )将始终小于列表长度。

Looks like that the dependent types — or something like them — are necessary for such guarantee, and we can't get the same from Rust, right?

看起来,依赖类型(或类似的类型)对于这种保证是必需的,我们不能从Rust中获得相同的信息,对吗?

Meet the typenum. Using it, we can write our function in the following way:

符合typenum 。 使用它,我们可以通过以下方式编写函数:

use generic_array::{ArrayLength, GenericArray};
use typenum::{IsLess, Unsigned, B1};
trait UnsignedLessThan<T> {
    fn as_usize(&self) -> usize;
}

impl<Less, More> UnsignedLessThan<More> for Less
where
    Less: IsLess<More, Output = B1>,
    Less: Unsigned,
{
    fn as_usize(&self) -> usize {
        <Self as Unsigned>::USIZE
    }
}

fn foo<El, Size>(x: GenericArray<El, Size>, y: El) -> Option<Box<dyn UnsignedLessThan<Size>>>
where
    El: Eq,
    Size: ArrayLength<El>,
{
    // Implementation elided 
}

"What is this black magic?!" — you could ask. And you're right: typenum is a black magic, and any attempts to use it are even more magic. But this function signature is fairly concrete.

“这是什么黑魔法?!” -你可以问。 而且您是对的:typenum 一个黑魔法,使用它的任何尝试都更加神奇。 但是此功能签名是相当具体的。

  • It takes an array of El's with the length Size and one more El.

    它需要一组长度为Size的El's和另一个El。
  • It returns an Option, which, if it is Some,

    它返回一个Option,如果是Some,

    • holds a trait object, based on the UnsignedLessThan<Size> trait;

      拥有一个基于UnsignedLessThan<Size>特征的特征对象

    • and the UnsignedLessThan<T> is implemented wherever Unsigned and IsLess<T> are implemented and IsLess<T> returns B1, i.e. true.

      UnsignedLessThan<T>任何地方实现UnsignedIsLess<T>被实现和IsLess<T>返回B1,即如此。

In other words, this function is guaranteed to return unsigned integer less than the array size (strictly speaking, it returns the trait object, but we can call as_usize method and get the integer).

换句话说, 保证此函数返回小于数组大小的无符号整数(严格来说,它返回trait对象,但是我们可以调用as_usize方法来获取整数)。

I can now speak of two major caveats:

我现在可以说两个主要警告:

  1. We can get a loss of performance. If somehow this function will be on the "hot" path of the program, the constant dynamic dispatches might slow down the whole process. This might not be a large concern, in fact, but there's another:

    我们会损失性能。 如果此函数以某种方式位于程序的“热门”路径上,则恒定的动态调度可能会减慢整个过程。 实际上,这可能不是一个大问题,但是还有另一个问题:
  2. For this function to compile, we must either write the proof of its correctness right inside it, or trick the type system with some unsafe. The former is fairly complex, and the latter is just cheating.

    为了编译该函数,我们必须在其内部编写其正确性证明,或者使用一些unsafe欺骗类型系统。 前者相当复杂,而后者只是作弊。

结论 (Conclusion)

Of course, in practice we'll generally use either the second approach (with generic slice) or the approach in spoiler (with iterator). All subsequent discussion is probably not of any practical interest and is here only as an exercise with types.

当然,在实践中,我们通常将使用第二种方法(带有通用切片)或扰流器中的方法(带有迭代器)。 随后的所有讨论可能都没有任何实际意义,在这里仅是对类型的练习。

Anyway, the fact that the Rust type system can emulate the feature from the stronger Idris type system, as for me, is fairly impressive by itself.

无论如何,就我而言,Rust类型的系统可以从更强大的Idris类型的系统中模拟功能这一事实本身就给人留下了深刻的印象。

翻译自: https://habr.com/en/post/468271/

rust 测试 指定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值