自从最初的const泛型RFC被接受以来已有3年多的时间了,Rust beta频道现已提供const泛型的第一个版本!它将在1.51版本中提供,该版本预计将于2021年3月25日发布。Const泛型是Rust中最受期待的功能之一,我们为人们开始使用该功能感到兴奋。添加此语言后,语言的功能增强了。
即使您不知道什么是const泛型(在这种情况下,请继续阅读!),您也可能会从中受益:const泛型已在Rust标准库中用于改善数组的人体工程学和诊断;下面的更多内容。
随着const泛型达到beta,让我们快速了解一下实际稳定的内容,这实际上意味着什么以及下一步。
const泛型是泛型参数,其范围是常量值,而不是类型或生存期。例如,这允许使用整数对类型进行参数化。实际上,自Rust早期开发以来就有一个const泛型类型的例子:数组类型[T; N],对于某些类型T和N:使用。但是,以前没有办法对任意大小的数组进行抽象:如果要为任何大小的数组实现特征,则必须为每个可能的值手动进行。长期以来,由于这个问题,甚至用于数组的标准库方法也被限制为最大长度为32的数组。最终在Rust 1.47中取消了此限制-const泛型使这一更改成为可能。
这是一个使用const泛型的类型和实现的示例:一种包装一对大小相同的数组的类型。
struct ArrayPair< T,const N:usize> {左:[T; N],右:[T; N],} impl< T:调试,常量N:usize>调试ArrayPair< T,N> {// ...}
故意对const泛型的第一次迭代进行了限制:换句话说,此版本是const泛型的MVP(最小可行产品)。此决定是由于通用const泛型的额外复杂性(通用const泛型的实现尚未完成,但我们认为1.51中的const泛型已经非常有用)以及逐渐引入大功能的愿望所致。 ,以获取有关任何潜在缺点和困难的经验。我们打算在Rust的未来版本中取消这些要求:请参阅下一步。
目前,唯一可以用作const泛型参数类型的类型是整数(即有符号和无符号整数,包括isize和usize)以及char和bool的类型。这涵盖了const的主要用例,即对数组进行抽象。将来,将取消此限制,以允许使用更复杂的类型,例如& str和用户定义的类型。
fn foo< const N:usize>(){} fn bar< T,const M:usize>(){foo ::< M>(); //好的:“ M”是const参数foo ::< 2021>(); //好的:`2021`是字面的foo ::< {20 * 100 + 20 * 10 + 1}>(); //好的:const表达式不包含通用参数foo ::< {M + 1}>(); //错误:const表达式包含通用参数`M` foo ::< {std :: mem :: size_of ::< T>()}>(); //错误:const表达式包含通用参数`T` let _:[u8; M]; //好的:“ M”是const参数let _:[u8; std :: mem :: size_of ::< T>()]; //错误:const表达式包含通用参数`T`}
除了上述语言更改之外,我们还开始利用const泛型向标准库添加方法。虽然大多数人尚未准备好在此版本中进行稳定化,但有一种方法已稳定下来。 array :: IntoIter允许通过值而不是通过引用来迭代数组,从而解决了一个重大缺陷。尽管仍然存在必须解决的向后兼容性问题,但仍在继续讨论是否可以直接为数组实现IntoIterator的可能性。 IntoIter :: new是一种临时解决方案,可大大简化数组的处理。
使用std :: array; fn needs_vec(v:Vec< i32>){// ...}让arr = [vec![0,1],vec![1、2、3],vec![3] ];对于数组中的元素:: IntoIter :: new(arr){needs_vec(elem);}
通用参数当前必须按特定顺序排列:生存期,类型,常量。但是,这会在尝试将默认参数与const参数一起使用时造成困难。为了使编译器知道哪个泛型参数,任何默认参数都必须放在最后。这两个限制-"类型位于consts&#34之前,而" defaults位于最后#" -具有默认类型参数和const参数的定义彼此冲突。
解决方案是放宽排序约束,以便const参数可以在类型参数之前。但是,事实证明,实现此更改涉及一些微妙的问题,因为Rust编译器当前对参数排序进行了假设,需要删除一些细节。
鉴于围绕const参数默认值的类似设计问题,当前在1.51版中也不支持这些问题。但是,解决以上参数排序问题也将取消阻止const默认值。
从理论上讲,要使一个类型有效,就必须作为const参数的类型,我们必须能够在编译时比较该类型的值。此外,价值观的平等应该表现得很好(即,它应该是确定性的,反身的,对称的和可传递的)。为了保证这些属性,在const泛型RFC中引入了结构相等的概念:本质上,它包括任何带有#[derive(PartialEq,Eq)]且其成员也满足结构相等的类型。
确切的结构平等行为以及实施的前提条件仍然存在一些问题。原始类型明显更简单,这允许在更通用的类型之前用于稳定这些类型的const泛型。
支持复杂的表达式涉及多个复杂性。 Nightly通道中提供了一个功能标志feature(const_evaluatable_checked),该标志启用了对const泛型的复杂表达式支持版本。
一个困难在于必须使用某种方法来比较未评估的常量,因为编译器不会自动知道两个语法上相同的表达式实际上是相等的。这涉及一种关于表达式的符号推理,这通常是一个复杂的问题。
//这两个表达式`N +1'和`N +1'在编译器中是不同的实体,因此我们需要一种方法来检查//是否应被视为相等。fn foo< const N:usize>( )-> [u8; N +1] {[u8; N +1]}
fn split_first< T,const N:usize>(arr:[T; N])-> (T,[T; N-1]){// ...} fn generic_function< const M:usize>(arr:[i32; M]){// ... let(head,tail)= split_first( arr); // ...}
如果没有办法在此处限制M的可能值,则在评估0-1时(在声明时未捕获),调用generic_function ::< 0>()将导致错误,因此对于下游用户可能会意外失败。
存在一些关于如何精确表达这些界限的设计问题,在稳定复杂的const参数之前需要解决这些问题。 有了如此重要的新功能,可能会有一些粗糙的边缘。 如果遇到任何问题,即使它只是一个令人迷惑的错误消息,也请打开一个问题! 我们希望用户体验是最好的-现在,对于const泛型的下一次迭代,任何问题现在都可能变得更加重要。