拆分 const 泛型特性

2021 年 9 月 6 日 · lcnr 代表 Const 泛型项目组

在 1.51 版本中稳定了 const 泛型 MVP 之后,const 泛型项目组继续致力于 const 泛型的工作。这项工作的大部分内容都由特性门 `const_generics` 和 `const_evaluatable_checked` 保护。随着时间的推移,`const_generics` 特性本身变得相当无用,而 `const_evaluatable_checked` 的名称并没有真正捕捉到这个特性的预期用途。

为了改进这一点,我们最近移除了 `const_generics`、`lazy_normalization_consts` 和 `const_evaluatable_checked` 特性。它们已被 `feature(adt_const_params)` 和 `feature(generic_const_exprs)` 取代。

由于 const 泛型有很多内容,这里快速概述一下新的和已有的特性,以及它们要稳定还需要做多少工作

feature(adt_const_params)

在稳定版中,只允许整数、`char` 和 `bool` 作为 const 参数的类型。此特性允许其他类型,例如 `&'static str` 和用户定义的类型。

#![feature(adt_const_params)]

#[derive(PartialEq, Eq)]
enum ImageFormat {
    Rgb8,
    Rgba8,
    // ...c
}

struct Image<const FORMAT: ImageFormat> {
    // ...
}

impl Image<{ ImageFormat::Rgba }> {
    fn alpha(&self, pixel: PixelLocation) -> u8 {
        // ...
    }
}

请注意,即使有了此特性,泛型 const 参数类型,例如 `struct Foo<T, const N: T> { ... }`,也是禁止的。虽然希望允许这样做,但这会增加超出我们当前能力的额外复杂性。

稳定化仍然存在两个主要障碍

第一个是向 valtrees 的过渡。Valtrees 是将值表示为带有整数节点的树,简化了我们与更复杂类型的交互方式。

此外,我们还必须弄清楚我们*甚至想*允许哪些类型作为 const 参数类型。这与关于“结构匹配”的讨论相关,该讨论仍在进行中。

虽然上面提到的问题绝对不是微不足道的,但它绝对有可能在几个月内为稳定化做好准备。

feature(generic_const_exprs)

在没有任何不稳定的特性下,const 参数必须是一个完全具体的表达式或一个泛型参数本身,因此像 `N + 1` 这样的常量是被禁止的。有了这个特性,可以使用泛型参数的表达式。

#![feature(generic_const_exprs)]

fn split_first<T, const N: usize>(arr: [T; N]) -> (T, [T; N - 1]) {
    // ...
}

struct BitSet<const SIZE: usize>
where
    [u8; (SIZE + 7) / 8]: Sized,
{
    storage: [u8; (SIZE + 7) / 8],
}

我们目前要求用户添加断言泛型常量成功求值的边界。对于项的 API 中可见的所有常量,这些边界是隐式添加的。

如果类型为 `Foo` 的常量表达式 `expr` 在其他情况下不会在 `where` 子句或函数签名中使用,我们会在项的 `where` 子句中添加一个提及 `expr` 的不相关的边界。为此,可以定义 `struct Evaluatable<const N: Foo>;` 并使用 `Evaluatable<{ expr }>: ` 作为边界。如果 `expr` 的类型为 `usize`,我们倾向于为此使用 `[u8; expr]:` 或 `[u8; expr]: Sized`。虽然我们很可能会在将来为此类边界添加专用语法,但我们会在该特性的其余部分更成熟之前等待。

此特性距离稳定版还很远,并且存在一些重大未解决的问题。特别是对于 `where` 边界内的常量,我们必须修复许多微妙的错误和向后不兼容问题,然后才能考虑如何稳定化它。

feature(const_generics_defaults)

与类型参数默认值类似,此特性增加了为 const 参数声明默认值的能力。

#![feature(const_generics_defaults)]

struct ArrayStorage<T, const N: usize = 2> {
    arr: [T; N],
}

impl<T> ArrayStorage<T> {
    fn new(a: T, b: T) -> ArrayStorage<T> {
        ArrayStorage {
            arr: [a, b],
        }
    }
}

为了允许类型参数默认值与 const 参数在同一列表中,我们还打算取消类型和 const 参数的排序限制,允许 `struct Foo<const N: usize, T = [u32; N]> { ... }`。

此特性几乎已准备好稳定化,目前受阻于找出稳定化报告的任何潜在边缘情况。

feature(generic_arg_infer)

虽然已经可以在主体内部使用通配符 `_` 作为类型参数,但对于 const 参数而言并非如此。此特性为常量添加了此功能。

#![feature(generic_arg_infer)]
fn array_from<T, U, const N: usize>(arr: [T; N]) -> [U; N]
where
    U: From<T>,
{
    arr.map(From::from)
}

fn main() {
    let x = ["this", "is", "a", "six", "element", "array"];
    // using `_` for the parameter `N` lets
    // the compiler infer the correct value
    let _y = array_from::<_, String, _>(x);
}

此特性尚未准备好稳定化,尽管此处没有任何已知的大型障碍。要自信地稳定它,我们可能需要进行一些大型重构,因为目前的设置在某些方面感觉相当脆弱。