6

I have a Vec of nontrivial types with a size I am certain of. I need to convert this into fixed size array. Ideally I would like to do this

  1. without copying the data over - I would like to consume the Vec
  2. without preinitializing the array with zero data because that would be a waste of CPU cycles

Question written as code:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = vec![
        Point { x: 1, y: 2 },
        Point { x: 3, y: 4 },
        Point { x: 5, y: 6 },
    ];

    // I would like this to be an array of points
    let array: [Point; 3] = ???;
}

This seems like a trivial issue, however I have not been able to find satisfactory solution in Vec docs, slicing sections of Rust Books or by Googling. Only thing that I found is to first initialize the array with zero data and later copy all elements over from Vec, however this does not satisfy my requirements.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Fireant
  • 88
  • 1
  • 3
  • 7
  • 1
    Might be a duplicate of https://stackoverflow.com/questions/31360993/what-is-the-proper-way-to-initialize-a-fixed-length-array and https://stackoverflow.com/questions/44182269/initialize-array-holding-struct-more-efficiently. – ArtemGr Mar 16 '18 at 19:49
  • Not a duplicate of https://stackoverflow.com/questions/31360993/what-is-the-proper-way-to-initialize-a-fixed-length-array, however it can serve as part of the solution, albeit using unsafe keyword. That said I would be surprised if there was not a simpler more elegant solution – Fireant Mar 16 '18 at 19:51
  • Without dynamic length (alloca) arrays are less useful on their own, you'd usually see some kind of higher-level structure used (https://github.com/servo/rust-smallvec ?) instead. I'd expect a more ergonomic solution to arise when the dynamic length arrays appear. – ArtemGr Mar 16 '18 at 20:06
  • 2
    To make the question more specific: should it support arrays of a specific length, or any length? It is evidently not trivial to write a safe and simple interface for this operation. For a single array length like `[T; 3]`, there will be simple solutions. – bluss Mar 16 '18 at 20:12
  • Yes, fixed length array is intended and wanted while dynamic length is not. In an API fixed length array specifies contract for the shape of the data passed. For example function create_triangle will take fixed length array of 3 points because triangle is defined by 3 points. It cannot take array of points of any other length. So I suppose that one could say that array length adds additional information about the usage (which also means that the length does not need to be verified at runtime) – Fireant Mar 16 '18 at 20:16

3 Answers3

5

Doing this correctly is exceedingly difficult. The problem lies in properly handling a panic when there's a partially uninitialized array. If the type inside the array implements Drop, then it would access uninitialized memory, causing undefined behavior.

The easiest, safest way is to use arrayvec:

extern crate arrayvec;

use arrayvec::ArrayVec;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = vec![
        Point { x: 1, y: 2 },
        Point { x: 3, y: 4 },
        Point { x: 5, y: 6 },
    ];

    let array: ArrayVec<_> = points.into_iter().collect();
    let array: [Point; 3] = array.into_inner().unwrap();
    println!("{:?}", array);
}

Beware this only works for specific sizes of arrays because Rust does not yet have generic integers. into_inner also has a performance warning you should pay attention to.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
3

There is also try_into?

use std::convert::TryInto;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let v: Vec<Point> = vec![Point { x: 1, y: 1 }, Point { x: 2, y: 2 }];
    let a: &[Point; 2] = v[..].try_into().unwrap();
    println!("{:?}", a);
}

It borrows immutably so the Vec is not consumed.

Erik Arvidsson
  • 915
  • 9
  • 10
1

Just for fun, here are examples that shows that safe Rust gives us ways of doing it for small specific sizes, for example like this:

/// Return the array inside Some(_), or None if there were too few elements
pub fn take_array3<T>(v: &mut Vec<T>) -> Option<[T; 3]> {
    let mut iter = v.drain(..);
    if let (Some(x), Some(y), Some(z))
        = (iter.next(), iter.next(), iter.next())
    {
        return Some([x, y, z]);
    }
    None
}

/// Convert a Vec of length 3 to an array.
///
/// Panics if the Vec is not of the exact required length
pub fn into_array3<T>(mut v: Vec<T>) -> [T; 3] {
    assert_eq!(v.len(), 3);
    let z = v.remove(2);
    let y = v.remove(1);
    let x = v.remove(0);
    [x, y, z]
}

The basic ways of having a Vec give you back ownership of its elements are remove, pop, drain, into_iter, etc.

bluss
  • 12,472
  • 1
  • 49
  • 48