Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create and initialize an immutable array?

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?

like image 797
Max Yankov Avatar asked Oct 18 '14 00:10

Max Yankov


People also ask

How do you create an immutable array?

Obtain the desired array. Convert it into a list object using the asList() method. Pass the obtained list as a parameter to the unmodifiableList() method.

How do you create an immutable array in Java?

There is one way to make an immutable array in Java: final String[] IMMUTABLE = new String[0]; Arrays with 0 elements (obviously) cannot be mutated. This can actually come in handy if you are using the List.

What is an immutable array?

An immutable array or object is a unique copy of the original that, when manipulated, does not affect the original.

What is immutable array Java?

An immutable data type can't be changed once it's created. The more restriction(like immutability) you have on a class, the better. For example, In Java, String, Integer, Double are Immutable classes, while StringBuilder, Stack, and Java array are Mutable.


1 Answers

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.

like image 164
Francis Gagné Avatar answered Sep 23 '22 06:09

Francis Gagné