mrnateriver
mrnateriver

Reputation: 2112

How to allocate structs on the heap without taking up space on the stack in stable Rust?

In this code...

struct Test { a: i32, b: i64 }
    
fn foo() -> Box<Test> {              // Stack frame:
    let v = Test { a: 123, b: 456 }; // 12 bytes
    Box::new(v)                      // `usize` bytes (`*const T` in `Box`)
}

... as far as I understand (ignoring possible optimizations), v gets allocated on the stack and then copied to the heap, before being returned in a Box.

And this code...

fn foo() -> Box<Test> {
    Box::new(Test { a: 123, b: 456 })
}

...shouldn't be any different, presumably, since there should be a temporary variable for struct allocation (assuming compiler doesn't have any special semantics for the instantiation expression inside Box::new()).

I've found Do values in return position always get allocated in the parents stack frame or receiving Box?. Regarding my specific question, it only proposes the experimental box syntax, but mostly talks about compiler optimizations (copy elision).

So my question remains: using stable Rust, how does one allocate structs directly on the heap, without relying on compiler optimizations?

Upvotes: 21

Views: 9208

Answers (5)

cafce25
cafce25

Reputation: 27218

With Rust 1.82 and above one can use &raw mut to avoid intermediary references as in mrnaterivers answer, the Allocator is still unstable so the use of std::alloc::alloc remains.

#[derive(Debug)]
struct Test {
    a: i64,
    b: &'static str,
}

fn main() {
    use std::alloc::{alloc, Layout};
    use std::ptr::addr_of_mut;

    let layout = Layout::new::<Test>();

    let bx = unsafe {
        let ptr = alloc(layout) as *mut Test;

        (&raw mut (*ptr).a).write(42);
        (&raw mut (*ptr).b).write("testing");

        Box::from_raw(ptr)
    };
    println!("{:?}", bx);
}

From Rust 1.51 to 1.82 you had to use addr_of_mut! to the same effect.

Upvotes: 3

If your data allows any byte pattern, you can just recast an arbitrary region of memory to your type.

We use the ByteMuck crate here, to ensure safety. It provides traits that ensure this function can only be called for types where any existing memory pattern is invalid.

use bytemuck;

pub fn unsafe_allocate<T: Copy + bytemuck::AnyBitPattern>() -> Box<T> {
    let mut grid_box: Box<T>;
    unsafe {
        use std::alloc::{alloc_zeroed, dealloc, Layout};
        let layout = Layout::new::<T>();
        let ptr = alloc_zeroed(layout) as *mut T;
        grid_box = Box::from_raw(ptr);
    }
    return grid_box;
}

This will create a region in memory automatically sized after T and unsafely convince Rust that that memory region is an actual T value. The memory may contain arbitrary data; you should not assume all values are 0.

Example use:

let mut triangles: Box<[Triangle; 100000]> = unsafe_allocate::<[Triangle; 100000]>();

You may need to [derive(bytemuck::AnyBitPattern)] for triangle. It will only work if Triangle is a "dumb" type where no bit pattern causes undefined behavior.

Upvotes: -1

mcarton
mcarton

Reputation: 29981

You seem to be looking for the box_syntax feature, however as of Rust 1.39.0 it is not stable and only available with a nightly compiler. It also seems like this feature will not be stabilized any time soon, and might have a different design if it ever gets stabilized.

On a nightly compiler, you can write:

#![feature(box_syntax)]

struct Test { a: i32, b: i64 }

fn foo() -> Box<Test> {
    box Test { a: 123, b: 456 }
}

Upvotes: 9

mrnateriver
mrnateriver

Reputation: 2112

As of Rust 1.39, there seems to be only one way in stable to allocate memory on the heap directly - by using std::alloc::alloc (note that the docs state that it is expected to be deprecated). It's reasonably unsafe.

Example:

#[derive(Debug)]
struct Test {
    a: i64,
    b: &'static str,
}

fn main() {
    use std::alloc::{alloc, dealloc, Layout};

    unsafe {
        let layout = Layout::new::<Test>();
        let ptr = alloc(layout) as *mut Test;

        (*ptr).a = 42;
        (*ptr).b = "testing";

        let bx = Box::from_raw(ptr);

        println!("{:?}", bx);
    }
}

This approach is used in the unstable method Box::new_uninit.

It turns out there's even a crate for avoiding memcpy calls (among other things): copyless. This crate also uses an approach based on this.

Upvotes: 11

Shepmaster
Shepmaster

Reputation: 430358

Is there a way to allocate directly to the heap without box?

No. If there was, it wouldn't need a language change.

People tend to avoid this by using the unstable syntax indirectly, such as by using one of the standard containers which, in turn, uses it internally.

See also:

Upvotes: 3

Related Questions