From the doc it says:
pub fn new(x: T) -> Box<T>
Allocates memory on the heap and then places
x
into it.
But "place" is a tricky word. If we write
let arr_boxed = Box::new([0;1000]);
Will the [0;1000]
be initialized on the heap in-place?
If we write
let arr = [0;1000];
let arr_boxed = Box::new(arr);
Will the compiler be smart enough to initialize the [0;1000]
on the heap in the first place?
Will Box::new() make a copy from stack to heap?
Sometimes. The Rust language does not guarantee this optimization to happen, and seems to leave it up to LLVM to figure this out. Because of this, it doesn't matter at all if you initialize the array first and then pass it, as that is essentially the same thing for the backend.
In practice, performance will depend on the case. The example you gave is actually special, because the data is all zeroes:
pub fn foo() -> Box<[i32; 1000]> {
return Box::new([0; 1000]);
}
In my testing, the compiler was able to turn that into an allocation + a call to memset
on the heap data.
Note: only with optimizations turned on though. In debug mode it will copy.
On the other hand, you might want to initialize your data with a known value:
pub fn bar(v: i32) -> Box<[i32; 1000]> {
return Box::new([v; 1000]);
}
Much to my horror, the compiler decides
to initialize the entire data on the stack, and then call memcpy
. (At least it unrolled the fill loop) :).
This happens even for really large data like [v; 100000]
, which will crash your program with a stack overflow. Using a compile time known (non zero) literal like [64; 100000]
behaves the same way.
If you really want to make sure, you could do something like this:
pub fn baz(v: i32) -> Box<[i32; 1000]>{
unsafe {
let b = std::alloc::alloc(
std::alloc::Layout::array::<i32>(1000).unwrap_unchecked()
) as *mut i32;
for i in 0..1000 {
*b.add(i) = v;
}
Box::from_raw(b as *mut [i32; 1000])
}
}
which does the right thing.
A safe version of baz
would be:
use std::convert::TryInto;
pub fn quux(v: i32) -> Box<[i32; 1000]> {
let mut b = Vec::with_capacity(1000);
b.extend(std::iter::repeat(v).take(1000));
b.into_boxed_slice().try_into().unwrap()
}
Which the compiler optimizes quite nicely, to essentially the identical assembly as baz
.
Even shorter would be
vec![v; 1000].into_boxed_slice().try_into::<Box<[i32; 1000]>>().unwrap()
which is probably the best version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With