Why is Box::new_uninit_slice(len) safe?

Hi!

I am quite new with RUST and do some experiments to understand the borrow checker and memory management. I have written the following code:

use std::mem::MaybeUninit;

#[derive(Debug)]
struct Droppable {
    value: u32,
}

impl Drop for Droppable {
    fn drop(&mut self) {
        println!("dropping {}", self.value);
    }
}

fn main() {
    let len = 4;

    let mut b_values: Box<[MaybeUninit<Droppable>]> = Box::new_uninit_slice(len);
    b_values[0].write(Droppable { value: 0});
    b_values[1].write(Droppable { value: 1});
    unsafe { b_values.assume_init()};
    println!("done");
}

I know this is "bad code", as only the first two of four elements in the array are initialized. The output is (or similar):

dropping 0
dropping 1
dropping 7077989
dropping 3276851
done

Not surprising, as I never initialized member 2 and 3.

When I remove the line

    unsafe { b_values.assume_init()};

no element is dropped. Not surprising, as I never declare the memory as initialized, to make the borrow "responsible" for that memory.

But why I am allowed to write this code? In my expectation

Box::new_uninit_slice(len);

should be unsafe to forbid this kind of bug.

Regards

Roger

I don't see how making new_uninit_slice unsafe would prevent UB if you assume you initialised memory correctly, even if you didn't. Creating the boxed slice is not inherently unsafe. However, assuming that memory is initialised properly is, as this relies on the logic of your program to uphold the invariant of correctly initialised memory.

1 Like

Because memory leaks are not “unsafe”, essentially. Yes, they are undesirable and Rust tries not to make them easy to do, but they are not unsafe. Even Box::leak is safe!

Because it's essentially impossible to prevent them with compile-time checking. Even tracing GC-based languages have them (Java marketing, famously, claimed that memory leaks are impossible in Java… thus they had to rename them into “an unintended object retention”… which is, of course, the exact same thing under a different name).

And to create anything “bad” (as in: undefined behavior) you would need to write some unsafe code which mean marking Box::new_uninit_slice as unsafe would just increase amount of unsafe code without adding any safety to the program.

4 Likes

@khimru: Thanks, that's it: I misinterpreted unsafe. It protects you from accessing unitialized memory, but not from leaking.

And regarding Java: It's a leak when you keep references you don't want to keep. A GC is helpful handling cyclic references, that's more difficult in RUST when using Rc or Arc.

Quite often cycles are a sign of a suboptimal design choice, and the right solution is to refactor to avoid them.

There are of course exceptions, for example if you doing graph theory stuff with cyclic graphs. Though you can represent that too differently: by using node indices or an adjecency matrix. Those representations might actually be much faster for a modern CPU as pointer chasing is slow on modern CPUs.

In general, think through the design one more time if you find yourself doing cyclic structures.

I wouldn't put it like that, actually. Cycles are incredibly common and they are normal way of designing things… what's “suboptimal” are data structures where one couldn't resolve cycles by making some edges “strong” and some “weak”: that means that you don't have a predefined hierarchy in your design and are only half-step from here to the “an unintended object retention”. If that happens then you need to invent some other way of making sure it wouldn't happen. Arenas are popular, and there are other, ad hoc approaches, too.

IOW: it's not cycles that are problematic, but more of “I have no idea how to avoid cycles here” structures. These are very rarely sound and often quite problematic.

Cycle or not cycle is not the question here. I wanted to know why a certain function is not unsafe, and that was answered quite good by @khimru.