When is it idiomatic to use Vectors vs Slices?

It's my understanding that arrays are to be used when we have a collection of known size, as opposed to vectors (slices), which may grow and shrink. But for that reason, arrays seem particularly prime targets for mutability, as opposed to vectors, which implement more iterator things. My question is this:

  1. Is there a more idiomatic way to build my array, than to initialize this mutable variable, and modify it's contents, and
  2. Are arrays the mutable, red-haired step child of Rust and functional programming writ large?
fn arr_from_usize(n: usize) -> [bool; 10] {
        let mut a: [bool; 10] = [false; 10];
        n.to_string()
            .chars()
            .map(|c| a[c.to_digit(10).unwrap() as usize] = true);
        a
    }

Arrays are a bit of a second-class citizen in Rust mainly because of ergonomic issues. Deficiencies in the language currently make it difficult or impossible to implement a trait for all array types, or to write generic functions that work with different sizes of arrays. This means that many standard traits and functions that ought to work for arrays don't, or they work only with arrays of certain sizes.

This will improve somewhat after const generics become stable (and after libraries are updated to take advantage of this).

8 Likes

I noticed that I would get unused code warnings on the function unless I did something silly, like change the last line to something that would consume the iterator, like this:

 fn arr_from_usize(n: usize) -> [bool; 10] {
        let mut a: [bool; 10] = [false; 10];
        n.to_string()
            .chars()
            .map(|c| a[c.to_digit(10).unwrap() as usize] = true)
            .last();
        a
    }

Is there a less silly way to do this?

Use for_each instead of map.

3 Likes

I don't think that's the case at all, when in doubt use Vec<T>. You only really use an array (e.g. [T; 42]) when the size is well-known ahead of the time and T is something "trivial" (e.g. an integer).

Values in Rust need to be initialized in one operation it's not possible to declare an array then initialize each element individually. The main reason for this is because an array is just a series of sequential elements, if something went wrong midway through initializing the array the array's destructor would have no way of knowing the rest of the items haven't been initialised and it'd try to destroy all items. Trying to destroy something which hasn't been initialized is no bueno and would lead to a bad time... I'm guessing this "atomic initialization" constraint is why arrays feel like the "mutable, red-haired step child".

If you want to initialize something one element at a time, you'll need a type which can keep track of which items have been initialized and which haven't. A Vec<T> will do all that bookkeeping for you and is the most idiomatic approach.

I hope you'll forgive me for being pedantic, but in Rust a "slice" (&[T]) means something quite different to a vector (Vec<T>).

A slice is a reference to a number of items laid out sequentially in memory and doesn't own the items, because the items aren't owned it doesn't matter whether a slice points to part of an array on the stack or items in a Vec<T> on the heap. A slice can't be resized, although if you are borrowing it mutably (&mut [T]) you are able to mutate the items within the slice.

On the other hand, a Vec<T> is an owning pointer to a number of items on the heap which can be dynamically resized and will be deallocated when the Vec<T> is dropped.

2 Likes

You may have a look at ::array-init and ::smallvec (or ::arrayvec if your type does not implement Default and are not worried about its using unsafe (e.g., you have audited it or trust other people that have)) for more ergonomic usages of arrays.

For instance, with array-init, something along the lines of:

fn arr_from_usize (n: usize) -> [bool; 10] 
{
    let digits = n.to_string().as_bytes(); // base 10
    ::array_init::from_fn(|i| {
        digits.contains(&(b'0' + i))
    })
}