Move out of `&'static mut T`

Hello, why code like this is not allowed?

struct NotDefaultNotCopy;

fn main() {
    let foo: &'static mut NotDefaultNotCopy = Box::leak(Box::new(NotDefaultNotCopy));
    let foo = *foo;
}

No matter what you do to a reference, there must always be a valid value behind the reference at the end. If what you wrote was allowed, then you would end up with a valid value of type NotDefaultNotCopy both in the second foo variable, and in the leaked Box. Since the value is not Copy, this kind of duplication is not allowed.

2 Likes

Well, it's not allowed in general, for any lifetime 'life in &' life T.

One reason is that given only the borrow, the compiler has no idea where else the referent of type T could be used. Moving out when it's used elsewhere would obviously not be great for reliability. This is a concern for any borrow, regardless of its lifetime.

If anything, the 'static lifetime would make it more likely that it would be in use elsewhere in the process.

For a &'static mut T exactly, you do know there are no other observers, modulo unsafe. There could be a function to unsafely read and return the T (and discard the &'static mut T).

But if &'static mut T is consumed, there is no reference anymore, and uninit bytes are never accessed again, in safe context, as this example is

For example, miri is happy with that code (but detects leak) playground

Here is code with more unsafe but with no miri leak warnings: playground

You could allow this operation with &'static mut T. But the language simply doesn't. There's only one type in the Rust language that can be consumed by the dereference operator, and that's Box. The &mut T type doesn't allow it, and there's no special case in the language for &'static mut T.

I mean, miri would be happy even if you actually duplicate the value. There's nothing unsound about that at the language level.

3 Likes

Would this duplicated value be ever accessible from safe code? If no, how is it different from move like let a = b or as argument passing?

Your take method is probably fine and will not actually allow duplication in practice. The Rust rules are designed to treat &'static mut T like any other &mut T, and that's why the language has no built-in way to do it.

As for duplication under miri, consider this example.

By the way, it may be related, in Lambda Rust (mathematical model of a subset of Rust) "owned pointer" represents full ownership of a value

RustBelt, section 3.3

Owned pointers ownn Ď„ are used to represent full ownership of (some part of) a heap allocation.
Because we model the stack using heap allocations, owned pointers also represent Rust’s local,
stack-allocated variables. As usual, Ď„ is the type of the pointee. Furthermore, n tracks the size of the
entire allocation. This can be different from the size of Ď„ for inner pointers that point into a larger
data structure.4 Still, most of the time, n is the size of Ď„ , in which case we omit the subscript.

I'd somehow missed the mut in the middle there. With that in there, I agree.

Still, what should the semantics of such a deref be? How should that even be implemented, given that the design of Deref and DerefMut enforce that * returns a &<SmartPtrType as Deref>::Target?

And then there's the runtime semantics. What should happen to the place containing the deref'ed value after an "owning deref" has taken place?

It raises design questions, is all I'm saying. Ones that probably are answerable (e.g. by taking inspiration from Box<T> asl @alice points out), but as of yet haven't been.

All the operator symbols are defined to be special cases for some built-in types, and use a trait for everything else. So &, &mut, and Box don't go through Deref because they're special cases. This also happens with the mathematical operators and numeric primitives.

The same thing that happens to memory when you move an owned value: nothing. It's statically guaranteed to not be used anymore.

This isn't ideal, since normally you want to free that memory when it comes from Box, but it's the only thing that could happen. As a result, it's probably not very useful.

1 Like

The general capability in terms of Box has been discussed various times (search for DerefMove and/or DerefPure). But personally, I feel giving the capabilities to &'static mut T directly seems too special-cased and surprising for the sake a niche need.

1 Like

@Ddystopia if you're going to cross post, tell us about it.

4 Likes

But how do you even reliably consume the &'static mut in the first place? One can create non-overlapping (hence valid) &'static mut _ references to the static item out of thin air, so this is 100% unsound and UB.

There is no static item here; the &’static mut in the example comes from Box::leak, which is a safe function.

How do you express "this &'static mut must come from a leaked Box" at the type system level, though? If you don't, and allow arbitrary statics to be passed to that function, the problem persists.

I guess this is a classic example of misusing/overusing references. If OP wants ownership of the value inside the Box (or the whole box), they should just pass the Box itself, instead of doing dirty tricks with unsafe and invalid references.

You don’t need to— &’static mut T means that you have exclusive access to T until the end of the program, regardless of how it was created. Among other things, that means that it’s impossible for any other code to observe any inconsistent state you might put T into.

Any code that uses unsafe to create an &’static mut T where this isn’t true is, as you point out, necessarily unsound. But that’s tangential to the question at hand.

3 Likes

I’m not sure if this is true: Given &’something T, you can generally put T into whatever inconsistent state you want as long as it’s valid by the time ’something ends. When ’something == ’static, that period where you can do inconsistent things extends until the end of program execution.

That's a contradiction, AFAIK - 'static references to the same value are always overlapping, no matter how they are actually used.

3 Likes