How to Initialize an array of Optional Mutable Objects?

Because mutable references are not Copy I get a compiler error when trying to use the array initialization syntax to create a new instance of an array of mutable values. How do I initialize this array with all Nones?

#![allow(warnings)]

fn main() {
    let array: [Option<&mut str>; 16] = [None; 16];
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `std::option::Option<&mut str>: std::marker::Copy` is not satisfied
 --> src/main.rs:4:41
  |
4 |     let array: [Option<&mut str>; 16] = [None; 16];
  |                                         ^^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `std::option::Option<&mut str>`
  |
  = help: the following implementations were found:
            <std::option::Option<T> as std::marker::Copy>
  = note: the `Copy` trait is required because the repeated element will be copied
  = note: this array initializer can be evaluated at compile-time, see issue #49147 <https://github.com/rust-lang/rust/issues/49147> for more information

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

I mean I could do this, but my real array has like 64 objects :eyes:

let array: [Option<&mut str>; 16] = [
    None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
    None,
]; 
let array: [Option<&mut str>; 16] = Default::default();
1 Like

Darn, there goes my suggestion...

let array: [Option<&mut str>; 16] = Default::default();

This works, but unfortunately even in 1.47 arrays of size greater than 32 still don't implement Default, so that won't work for your real case.

1 Like

Ah, bummer. I'm not sure if there's a way to use a macro or not. Feels like there is, but looping over numbers is not the same as looping over syntax in macros, so I might still have to type 64 arbitrary different items, probably just a letter, into the macro just to give it something to loop over.

Still, I might not need 64 elements, so Default::default() might work for now.

= unsafe { std::mem::zeroed() }

None is guaranteed to be zero for optional references.

2 Likes

Ah, OK, I figured that would probably be easy with unsafe, I was just looking at zeroed(), but it scared me when it wasn't valid for &mut or & types, but that didn't consider the Option in my case.

So for now I'll just use less than 64 elements, as I think 32 or maybe even less would probably be fine in my case ( It's like a "how many elements will users end up needing in practice?" question ), then I'll use the Default initialization.

If I end up needing more, I'll use zeroed().

After digging through the GitHub issue linked by the compiler error message, I came up with this, which works:

fn main() {
    const INIT:Option<&mut str> = None;
    let array: [Option<&mut str>; 1000] = [INIT; 1000];
}
6 Likes

Oh, perfect, thanks!

You may also find the techniques and crates in this article interesting:

1 Like

I'd prefer to manually specify all the Nones than using a single line of unsafe {}. Unless it spans thousands of lines(not elements), I can say it has less maintenance cost than the single line of unsafe {}.

Really, it's just a matter of copy-pasting. Fill your first line with reasonable amount of Nones, maybe 8 or 16, and copy-paste it until the compiler stop throw the error. You can't make a mistake, the compiler check the number of elements.

1024 = 16 * 64. Extra 64 lines are nothing. You can even put it into another file named constants.rs and never open it again. Oh, and never forget to add #![forbid(unsafe)] on the top of your lib.rs and add badge.

3 Likes

Well, I'll do that on the next crate that isn't my contribution to an ECS which requires unsafe to work. :wink: :+1:

All of the other options suggested so far are preferable to this. But at this point I already wrote it, so...

macro_rules! none {
    (@ () $($etc:tt)*) => { [ $($etc)* ] };
    (@ (0 $($tail:tt)*) $($etc:tt)*) => { none!(@ ($($tail)*) $($etc)* $($etc)*) };
    (@ (1 $($tail:tt)*) $($etc:tt)*) => { none!(@ ($($tail)*) None, $($etc)* $($etc)*) };
    (bin $($args:tt)*) => { none!(@ ($($args)*)) };
}

const ARRAY: [Option<&mut str>; 100] = none!(bin 1 1 0 0 1 0 0);
//                 binary representation of 100: ^^^^^^^^^^^^^

This technique is not my original idea, but I don't remember where I first saw it used, so I can't give proper credit.

6 Likes

I love it. I now know it's possible to loop over a binary representation of a number in macros. LOL.

You never know, it might come in handy one day.