指定可变长度静态切片的方法

Way to specify a static slice of variable length

假设我有一个具有以下签名的函数:

fn validate(samples: &[(&str, &[Token])])

其中 Token 是自定义枚举。 我希望能够按照这些思路写一些东西:

    let samples = vec![
        ("a string", &[Token::PLUS, Token::MINUS, Token::PLUS]),
        ("another string", &[Token::MUL]),
    ];
    validate(&samples);

但是这样的代码会产生类型不匹配的编译错误:

error: mismatched types:
expected `&[(&str, &[Token])]`,
   found `&collections::vec::Vec<(&str, &[Token; 3])>`

是否可以通过某种方式将具有静态长度的版本 (&[Token; 3]) 转换为静态切片 (&[Token])? 换句话说,我希望能够以类似于我指定 &str 的方式指定静态切片,作为某种 "slice literal".

还是我完全错了?

编辑: 简而言之,我想找到一种语法来创建一个具有静态生命周期(或至少与 samples 向量一样长的生命周期)的数组,以及它的 returns 切片。

类似于字符串的工作方式,只需键入 "a string" 即可获得 &'static str.

类型的引用

编辑2: @Pablo 的回答为我的特定问题提供了很好的解决方案,尽管这并不是我最初的意思。

我想我的想法可能不太可能,所以我暂时接受那个,除非出现更符合我最初想法的东西。

In short, I would like to find a syntax that creates an array with static lifetime (or at least a lifetime that is as long as the samples vector's one), and returns slice of it.

你想要这样的东西:

fn sliced(array: [Token; 3]) -> &'static [Token] { unimplemented!() }

因此您可以在您的示例中像这样使用它:

let samples: Vec<(&str, &[Token])> = vec![
    ("a string", sliced([Token::PLUS, Token::MINUS, Token::PLUS])),
    // ...

但是它有两个问题。第一个也是最明显的是你不能从一个不接受 static 引用的函数中得到一个 static 引用(在这种情况下它只会 return 它) .

因此,由于您希望切片至少与数组一样长寿,因此您可以声明一个 const/static 切片(这还需要一个 const/static 声明它的数组),或者先用 let 语句声明数组,然后再制作切片。 (这是我在下面的第一个替代方案中所做的。)如果您在 vec! 的使用中创建数组及其切片,数组将以 vec! 结束其生命周期,使切片无效.作为一个例子,考虑这个,由于同样的原因而失败:

fn main() {
    let slice;
    {
        let array: [u8; 3] = [1,2,3];
        slice = &array;
    }
}

sliced 函数的第二个问题是它的输入数组具有固定大小,而您通常希望处理任意大小的数组。但是,目前 Rust[1] 不支持此功能。您必须使用切片才能处理任意大小的数组。

那么,一种可能性是执行以下操作 [playpen]:

enum Token {
    PLUS,
    MINUS,
    MUL,
}

fn validate(samples: &[(&str, &[Token])]) {
    unimplemented!()
}    

fn main() {
    let tokens_0 = [Token::PLUS, Token::MINUS, Token::PLUS];
    let tokens_1 = [Token::MUL];
    let samples: Vec<(&str, &[Token])> = vec![
        ("a string", &tokens_0),
        ("another string", &tokens_1),
    ];
    validate(&samples);
}

这里有两个关于您的代码的更改。

其一,此代码依赖于通过引用数组 ([T; N]) 作为切片 (&[T]) 的隐式强制转换。这是 samples 声明为 Vec<(&str, &[Token])> 类型所要求的。这稍后在使用 vec! 时通过传递对数组的引用来满足,从而引发适当的强制转换。

第二,它在使用 vec! 宏之前创建了 Token 的数组,这保证了它们的寿命足以从它创建的 Vec 中引用,保持这些引用在 vec! 完成后有效。这在解决之前的类型不匹配后是必要的。


附录:

或者,为了方便起见,您可能更喜欢使用 Vec 而不是切片。考虑以下替代方案 [playpen]:

enum Token {
    PLUS,
    MINUS,
    MUL,
}

fn validate<T>(samples: &[(&str, T)]) where
    T: AsRef<[Token]>
{
    let _: &[Token] = samples[0].1.as_ref();

    unimplemented!()
}    

fn main() {
    let samples: Vec<(&str, Vec<Token>)> = vec![
        ("a string", vec![Token::PLUS, Token::MINUS, Token::PLUS]),
        ("another string", vec![Token::MUL]),
    ];
    validate(&samples);
}

在这种情况下,元组第二个元素上的 AsRef<[Token]> 接受任何类型,您可以从中获取 &[Token],提供 as_ref() 方法 returns 预期的参考。 Vec<Token> 就是这种类型的一个例子。


[1] “Rust 目前不支持超过数组类型大小的泛型。” [source]

注意:此答案在这种特定情况下无效,因为嵌套切片指向的数组不能比向量长寿,因为它们仅在各自表达式的持续时间内分配,因此不能将它们的切片存储在向量中。

正确的方法是将切片提升到上层并将它们放在向量之前,或者使用完全不同的结构,例如嵌套 Vecs。 .

中提供了所有这些示例

您需要这样做:

let samples = vec![
    ("a string", &[Token::PLUS, Token::MINUS, Token::PLUS] as &[_]),
    ("another string", &[Token::MUL] as &[_]),
];
validate(&samples);

当目标类型已知时,Rust 会自动将对数组 (&[T; n]) 的引用转换为切片 (&[T]),但在这种情况下,由于必要的 deref,类型推断无法正常工作强制转换,因此编译器无法推断出您需要切片而不是数组,也无法插入适当的转换,因此您需要明确指定类型。

另外,没有"static slice"这样的东西。最接近的实体是具有静态生命周期的切片,&'static [T],但据我所知,情况并非如此。