How do I create and initialize an immutable array?
Asked Answered
A

3

7

I want to create an array. I don't need the array to be mutable, and at the time of creation, I have all the information I need to calculate the i-th member of the array. However, can't figure out how to create an immutable array in Rust.

Here's what I have now:

let mut my_array: [f32; 4] = [0.0; 4];
for i in 0..4 {
    // some calculation, doesn't matter what exactly
    my_array[i] = some_function(i);
}

And here's what I want:

let my_array: [f32; 4] = array_factory!(4, some_function);

How can I achieve that in Rust?

Anisometric answered 18/10, 2014 at 0:9 Comment(0)
R
6

Here's the macro definition with sample usage:

macro_rules! array_factory(
    ($size: expr, $factory: expr) => ({
        unsafe fn get_item_ptr<T>(slice: *mut [T], index: usize) -> *mut T {
            (slice as *mut T).offset(index as isize)
        }

        let mut arr = ::std::mem::MaybeUninit::<[_; $size]>::uninit();
        unsafe {
            for i in 0..$size {
                ::std::ptr::write(get_item_ptr(arr.as_mut_ptr(), i), $factory(i));
            }
            arr.assume_init()
        }
    });
);

fn some_function(i: usize) -> f32 {
    i as f32 * 3.125
}

fn main() {
    let my_array: [f32; 4] = array_factory!(4, some_function);
    println!("{} {} {} {}", my_array[0], my_array[1], my_array[2], my_array[3]);
}

The macro's body is essentially your first version, but with a few changes:

  • The type annotation on the array variable is omitted, because it can be inferred.
  • The array is created uninitialized, because we're going to overwrite all values immediately anyway. Messing with uninitialized memory is unsafe, so we must operate on it from within an unsafe block. Here, we're using MaybeUninit, which was introduced in Rust 1.36 to replace mem::uninitialized1.
  • Items are assigned using std::ptr::write() due to the fact that the array is uninitialized. Assignment would try to drop an uninitialized value in the array; the effects depend on the array item type (for types that implement Copy, like f32, it has no effect; for other types, it could crash).
  • The macro body is a block expression (i.e. it's wrapped in braces), and that block ends with an expression that is not followed by a semicolon, arr.assume_init(). The result of that block expression is therefore arr.assume_init().

Instead of using unsafe features, we can make a safe version of this macro; however, it requires that the array item type implements the Default trait. Note that we must use normal assignment here, to ensure that the default values in the array are properly dropped.

macro_rules! array_factory(
    ($size: expr, $factory: expr) => ({
        let mut arr = [::std::default::Default::default(), ..$size];
        for i in 0..$size {
            arr[i] = $factory(i);
        }
        arr
    });
)

1 And for a good reason. The previous version of this answer, which used mem::uninitialized, was not memory-safe: if a panic occurred while initializing the array (because the factory function panicked), and the array's item type had a destructor, the compiler would insert code to call the destructor on every item in the array; even the items that were not initialized yet! MaybeUninit avoids this problem because it wraps the value being initialized in ManuallyDrop, which is a magic type in Rust that prevents the destructor from running automatically.

Repro answered 18/10, 2014 at 0:38 Comment(7)
+1 Nice! But I'm almost sure your assignment is wrong. You need to use ::std::ptr::write instead. Otherwise drop is called on uninitialized values.Adrien
I wonder whether [std::uninitialized(),..N] is a no-op or involves N function calls. I think a single std::uninitialized() for the whole array is a better approach.Adrien
Look at the generated asm on the playpen with -O2. It seems to be initializing the whole array with 2 moves in this example. With -O0, it is calling uninitialized(), but not in a loop.Scientistic
Thanks! But coming from C#, I have a habit of avoiding unsafe blocks as much as possible. (BTW, is it still a good habit in Rust or should I drop it?) Do I understand correctly that the only reason you used unsafe blocks is not to rely on Default::default() being implemented?Anisometric
@golergka: Avoiding unsafe is still a good habit. In this case it's used to avoid the issue of having to initialize the array with some dummy element.Adrien
@golergka: I used unsafe blocks for 2 reasons: not requiring the array item type to implement Default, and not writing default values to an array that we're going to immediately replace anyway. BTW, I edited my answer to include the safe version too, for completeness.Scientistic
Creating an array from a function should be a rust feature: you can be sure that everything will be initialized, it does not need to be unsafe !Macguiness
M
3

Now, there is a (pretty popular) crate to do that exact thing: array_init

use array_init::array_init;
let my_array: [f32; 4] = array_init(some_function);

PS:

There is a lot of discussion and evolution around creating abstractions around arrays inside the rust team.

For example, the map function for arrays is already available, and it will become stable in rust 1.55.

If you wanted to, you could implement your function with map:

#![feature(array_map)]
let mut i = 0usize;
result = [(); 4].map(|_| {v = some_function(i);i = i+1; v})

And there are even discussions around your particular problem, you can look here

Macguiness answered 25/7, 2021 at 16:0 Comment(2)
It's a mistake, I will edit my answer. Thanks !Macguiness
I updated my answer. Do you think my PS is more fair ?Macguiness
A
2

Try to make your macro expand to this:

let my_array = {
    let mut tmp: [f32, ..4u] = [0.0, ..4u];
    for i in range(0u, 4u) {
        tmp[i] = somefunction(i);
    }
    tmp
};

What I don't know is whether this is properly optimized to avoid moving tmp to my_array. But for 4 f32 values (128 bits) it probably does not make a significant difference.

Adrien answered 18/10, 2014 at 0:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.