How to create a big array filled with Option::<String>::None

Hmm, I think there must be someone has asked this question before, but plz forgive me that I cannot find it...
I want to do this:

Even thought Some(string) doesn't satisfied Copy obviously, but None does in logical. Is there any way to create that array without using unsafe code?

You can put constants in there even it's not Copy.

const NONE: Option<String> = None;
[NONE; 256]
4 Likes

If String is !Copy then that means all values of Option<String> (including None) are !Copy too.

It's a bit of a hack, but you could use something like array::map().

let a: [Option<String>; 256] = [0; 256].map(|_| None);

It looks like Default is only implemented for arrays with lengths up to 32 instead of for any const N: usize, so we can't write something like let x: [Option<String>; 256] = Default::default()... However, we can fake it using map() from above:

fn init_array<T, F, const N: usize>(mut initializer: F) -> [T; N]
where
    F: FnMut() -> T,
{
    [0; N].map(|_| initializer())
}

fn array_of_nones<T, const N: usize>() -> [Option<T>; N] {
    init_array(|| None)
}

fn main() {
    let x: [Option<String>; 256] = array_of_nones();
}

(playground)

It seems feasible, but won't it cause runtime overhead, because it creates [0;N]?

It can easily be optimized out since nobody actually reads those 0s.

Thanks for your solution, it works! But why Rust doesn't support using [Option::<String>::None;N] directly?

I did some playing around (mainly adding #[inline(never)] to array_of_nones()) and this is the assembly generated for array_of_nones<String, 256>():

playground::array_of_nones:
	mov	eax, 6152
	call	__rust_probestack
	sub	rsp, rax
	mov	eax, 168

.LBB5_1:
	mov	qword ptr [rsp + rax - 160], 0
	mov	qword ptr [rsp + rax - 136], 0
	mov	qword ptr [rsp + rax - 112], 0
	mov	qword ptr [rsp + rax - 88], 0
	mov	qword ptr [rsp + rax - 64], 0
	mov	qword ptr [rsp + rax - 40], 0
	mov	qword ptr [rsp + rax - 16], 0
	mov	qword ptr [rsp + rax + 8], 0
	add	rax, 192
	cmp	rax, 6312
	jne	.LBB5_1
	lea	rsi, [rsp + 8]
	mov	edx, 6144
	call	qword ptr [rip + memcpy@GOTPCREL]
	add	rsp, 6152
	ret

My understanding is that it creates enough space for the [Option<String>; 256] on the stack (after making sure we have enough stack space left), goes into a while-loop that fills the first 168 of every 192 bytes with zeroes (the last 24 bytes are padding due to Option's tag), then copies the temporary [Option<String>; 256] to the destination requested by the caller.

I would have expected there to be some sort of Return Value Optimisation to elide that intermediate copy and write directly to where the caller wants our return value to be, but other than that there is no actual [0; 256] constructed.

While this specific case seems obvious, supporting general [expr; N] for non-Copy types isn't that simple. The only way to support it would be to .clone() the first element to fill the rest slot. But we don't have any other such implicit cloning in the language outside of macros. Unlike the Copy, .clone() invokes arbitrary function defined by user, not the language, which can be a footgun for both performance and safety.

Well, I believe the original motivation to invent the whole ownership/lifetime system was to prevent duplicating arbitrary heavy values on assignment like C++ does. If the language invent the borrow checker to eliminate single copy, it would be a reasonable guess that it will not accept N copies for the single syntactical element.

2 Likes

You can also do [(); 256].map(|_| None) to make it extra clear that it’s just a dummy (and also guarantee that it takes zero space).

On nightly, the array::from_fn method was just added this week, allowing you to just write let arr: [Option<String>; 256] = std::array::from_fn(|_| None); but might not be stabilized any time soon.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.