Initializing array with a function that can panic without allocations

I was trying to initialize an array using a user provided callback function. So I figured I would use mem::unitialized to do (The Rustonomicon suggests that this is allowed, as it provided example of that function with arrays). This function works... sorta.

use std::mem;
use std::ptr;

fn init_array<T, F: Fn() -> T>(f: F) -> [T; 20] {
    unsafe {
        let mut arr: [T; 20] = mem::uninitialized();
        for item in &mut arr {
            ptr::write(item, f());
        }
        arr
    }
}

However, there is a possibility of panic occuring, as a function is provided by a quite panicky programmer. If that were to happen, and T would have a Drop implementation, then Drop would be called on uninitialized value, and it would likely lead to undefined behaviour, which as most C programmers will tell you is worst thing ever.

So an array could be wrapped in a type that has a Drop implementation whose purpose is to only drop elements that were assigned (while also storing array length). However, currently Rust appear doesn't provide a way to stop implicit drop calls for each structure element after calling drop function.

The Rustonomicon suggests to use an Option type as a workaround to make sure array value is None after leaving drop function. However, this doesn't exactly work. With null pointer optimization (caused in a type like Option<[&i32; 20]>), Option type tag is not written in output assembly, as Rust trusts the type to have a non-zero value (which normally would be the case).

Can somebody suggest me either how to do it with unsafe code, or better yet, a module that does so or a way to do it in a safe code without doing allocations (so storing values in Vec<T>, and then moving them is not an option).

There is https://crates.io/crates/nodrop

which is used by crates like https://crates.io/crates/init_with

You might want to take a look at them :slight_smile:

2 Likes

init_with no longer uses nodrop :slight_smile:

https://github.com/QuietMisdreavus/init-with-rs/commit/693634aa14f6ed9d9da20532bd7a4a2331d5655e

1 Like

Not calling drop on initialised variables would also be undefined behaviour.

I think it'd be just a mem leak, not UB (in the compiler sense).

1 Like

Not anymore, thread::scoped is no longer a thing due to not how calling drop on it would cause undefined behavior with it. These days, not calling drop has to be safe. Not that this is concern in this case, as you could easily store a counter of inserted values, and just iterate over that many elements to drop them.

https://github.com/rust-lang/rfcs/blob/master/text/1066-safe-mem-forget.md

Considering a crate I was creating was using procedural macros to generate code, I decided to just use an automatically generated array literal (in style of init_with as suggested by @steveklabnik, I wrote my own code however, to allow arrays bigger than 32 elements). This way I don't use unsafe, which is much better, as there is no risk of crashing.

https://github.com/xfix/enum-map/blob/0dd3bee4242980d5396649e5175d45faae5be86a/enum-map-derive/src/lib.rs#L61-L65