5 releases

0.1.4 May 28, 2021
0.1.3 May 28, 2021
0.1.2 May 27, 2021
0.1.1 May 27, 2021
0.1.0 May 27, 2021

#47 in #impl


Used in array_iter_tools

MIT license

12KB
146 lines

array_builder

ArrayBuilder impl for Rust. https://github.com/rust-lang/rfcs/pull/3131


lib.rs:

Dynamic array initialisation is very dangerous currently. The safest way is to initialize one with a default value

let mut array = [0; 32];
for i in 0..32 {
    array[i] = i;
}

This is not possible in general though. For any type [T; N], T either needs to be Copy, or there needs to be a const t: T. This is definitely not always the case.

The second problem is efficiency. In the example above, we are filling an array with zeros, just to replace them. While the compiler can sometimes optimise this away, it's nice to have the guarantee.

So, what's the alternative? How about MaybeUninit! Although, it's not that simple. Take the following example, which uses completely safe Rust! Can you spot the error?

let mut uninit: [MaybeUninit<String>; 8] = MaybeUninit::uninit_array();
uninit[0].write("foo".to_string());
uninit[1].write("bar".to_string());
uninit[2].write("baz".to_string());
panic!("oops");

Did you spot it? Right there is a memory leak. The key here is that MaybeUninit does not implement Drop. This makes sense since the value could be uninitialized, and calling Drop on an uninitialized value is undefined behaviour. The result of this is that the 3 String values we did initialize never got dropped! Now, this is safe according to Rust. Leaking memory is not undefined behaviour. But it's still not something we should promote.

What other options do we have? The only solution is to provide a new struct that wraps the array, and properly implements Drop. That way, if drop is called, we can make sure any initialized values get dropped properly. This is exactly what ArrayBuilder provides.

use array_builder::ArrayBuilder;
let mut uninit: ArrayBuilder<String, 8> = ArrayBuilder::new();
uninit.push("foo".to_string());
uninit.push("bar".to_string());
uninit.push("baz".to_string());
panic!("oops"); // ArrayBuilder drops the 3 values above for you
use array_builder::ArrayBuilder;
let mut uninit: ArrayBuilder<String, 3> = ArrayBuilder::new();
uninit.push("foo".to_string());
uninit.push("bar".to_string());
uninit.push("baz".to_string());
let array: [String; 3] = uninit.build().unwrap();

You can also take a peek at what the current set of initialised values are

use array_builder::ArrayBuilder;
let mut uninit: ArrayBuilder<usize, 4> = ArrayBuilder::new();
uninit.push(1);
uninit.push(2);
uninit.push(3);

// we can't build just yet
let mut uninit = uninit.build().unwrap_err();
let slice: &[usize] = &uninit;
assert_eq!(&[1, 2, 3], slice);

uninit.push(4);
assert_eq!([1, 2, 3, 4], uninit.build().unwrap());

No runtime deps