rusttype-mismatch

Way to specify a static slice of variable length


Let's say I have a function with following signature:

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

Where Token is a custom enum. I would like to be able to write something along those lines:

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

But code like this yields mismatched types compile error:

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

Is it possible to somehow convert the version with static length (&[Token; 3]) to a static slice (&[Token])? In other words, I would like to be able to specify a static slice in similar way I specify &str, as some kind of "slice literal".

Or I am doing it completely wrong?

EDIT: 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.

Something similar to how strings work, where just typing "a string" gives me reference of type &'static str.

EDIT2: @Pablo's answer provides pretty good solution to my particular problem, although it is not exactly what I have meant at first.

I guess that the exact thing I have in mind might not be possible, so I will just accept that one for now, unless something more in lines of my initial idea come around.


Solution

  • 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.

    You’d want something like this:

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

    So you could use it like this in your example:

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

    But there are two problems with it. The first and most glaring is that you can’t get a static reference out of a function which doesn’t take in a static reference (in which case it would just return it).

    Therefore, since you want a slice at least as long-lived as your array, either you declare a const/static slice (which requires also a const/static declaration of its array), or you declare the array with a let statement first, and then make the slice. (This is what is done at my first alternative, below.) If you create the array inside a use of vec!, together with its slice, the array end its life with vec!, rendering the slice invalid. As an illustration, consider this, which fails due to the same reason:

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

    The second problem with the sliced function is that its input array has a fixed size, and you’d want to work generically over arrays of arbitrary size. However, this is currently not supported by Rust[1]. You have to work with slices in order to deal with arrays of arbitrary size.

    One possibility, then, is to do the following [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);
    }
    

    There are two changes here with respect to your code.

    One, this code relies on implicit coercing of an array ([T; N]) as a slice (&[T]) by taking a reference to it. This is demanded by the declaration of samples as being of type Vec<(&str, &[Token])>. This is later satisfied, when using vec!, by passing references to the arrays, and thus eliciting the appropriate coercions.

    Two, it creates the arrays of Token before using the vec! macro, which guarantees that they’ll live enough to be referenced from within the Vec it creates, keeping these references valid after vec! is done. This is necessary after resolving the previous type mismatch.


    Addendum:

    Or, for convenience, you may prefer to use a Vec instead of slices. Consider the following alternative [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);
    }
    

    In this case, the AsRef<[Token]> bound on the second element of the tuple accepts any type from which you may take a &[Token], offering an as_ref() method which returns the expected reference. Vec<Token> is an example of such kind of type.


    [1] “Rust does not currently support generics over the size of an array type.” [source]