A unit is zero-sized, nothing will be written via the mutable reference of it, returning &mut () is theoretically sound. How to pass the compile, or am I wrong? Thanks.
I think you're misinterpreting what it is you wrote.
What the fn signature says is that it returns a mutable borrow, that lives for the entire life of the program, to a unit value.
Regardless of the type behind the borrow (i.e. it's not unique to ()), this can obviously never work when what is returned is a borrow to a stack-allocated value, which is popped as soon as the fn returns. If it were to compile, it would result in either a dangling reference, or UB, neither of which is desirable.
This is a good moment cut through the XY problem to ask: What is it you're trying to achieve with all this?
The value is zero-sized so this is in principle sound. In fact, Rust supports the almost identical empty mutable slice:
fn foo() -> &'static mut [i32] {
&mut []
}
There just isn't a comparable special case for (). I can't immediately think of a workaround other than writing unsafe code to construct the reference (which should be fine).
Static promotion of temporaries doesn't apply for () for some reason. It does apply for empty slices, for example, but notionally, that is pretty much a special case which shouldn't normally be allowed, unless the compiler was (as it is) made aware of the special nature of ZSTs.
Exactly this. And as far as I'm aware, no such special support exists. That that support exists for slices is... slightly baffling, in terms of use cases. Especially so considering that slices are not resizable, at least AFAIK.
If anyone would like to explain that to me, I'm all ears (eyes?).
I understand it will become a dangling reference if the stack-allocated value's size is not zero, but the point is the value's size is zero.
There is a way could avoid this, that is, return a mutable reference of a static invariant. There will be no data race, because () contains no data, in other words any () is guaranteed to be unchanged. But the compiler doesn't seem to know this situation, multiple mutable reference of a variant is not allowed, it requires unsafe code.
To static check a mutable reference cannot be used twice.
Here is how to construct your own perfectly good reference from scratch. [Update: but don't do this; Box::leak() is safe and just as good.]
fn mut_unit() -> &'static mut () {
let ptr: *mut () = core::ptr::NonNull::dangling().as_ptr();
// SAFETY:
// The pointer is a nonnull, aligned pointer to a ZST.
// All such pointers are valid; there are no requirements on the pointed-to
// memory because the pointer refers to zero bytes of it.
unsafe { &mut *ptr }
}
/// Run this under Miri to test
fn main() {
let a = mut_unit();
let b = mut_unit();
// If there were an aliasing problem, Miri would reject this
*a = ();
*b = ();
}
struct IterMut<'a, T> {
slice: &'a mut [T],
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// We can't get an `&'a mut T` from our shorter `&mut self` so we
// need to get the slice "out from under self" for a moment.
//
// (`take` works too, but for illustration)
let slice = std::mem::replace(&mut self.slice, &mut []);
match slice {
[] => None,
[first, slice @ ..] => {
self.slice = slice;
Some(first)
}
}
}
}
This example is confusing to me, why can't we get &'a mut T from &'1 self directly and must via replace function to put a empty slice at that place and take it back? What unsound thing would happen if getting a longer lived reference from a shorter lived reference is allowed (the type of the value is 'static)?
If you could get the longer lifetime out through the outer reference, you could do this:
fn next(&mut self) -> Option<Self::Item> {
if self.slice.len() == 0 {
None
} else {
// N.b. we didn't update `self.slice`
Some(&mut self.slice[0]) // A
}
}
// Elsewhere, B
let first = iter.next().unwrap();
let other_first = iter.next().unwrap();
And at point B you have two active &mut to the same element, which is instant UB. In fact, you also have that at point A already, where &mut self is active and can reach self.slice[0] and the return value is also pointing at self.slice[0], so calling next with a non-0 length is always UB -- no need to call it in a certain way.
More generally, you can never get something 'long out of a &'short mut &'long mut _, because once 'short expires, the underlying &'long mut must again have exclusive access to everything within it.
To avoid the UB, we need to "split" the borrow of the exclusive slice into the element we want to return and the rest of the slice. I used a slice pattern; another way would be split_first_mut.[1] In any case, the idea behind exclusive borrow splitting is that the multiple returned borrows must be non-overlapping, so we avoid the aliasing UB.
But we can't split the &'long mut through a &'short mut &'long mut, so we have to get ahold of the &'long mut _. That's why we used replace to get the slice "out from under self". This is the part where you need to be able to summon a &mut [] "out of nowhere" as a placeholder, as you can't move out of borrowed content (as that would leave uninitialized data behind a reference).
At that point we can split the borrow, and then put the non-overlapping remainder of the slice back.
You used to need unsafe to do this borrow splitting, but slice patterns make it possible without unsafe. ↩︎
On one hand, having static promotion work for ZSTs would be consistent, on the other I can't really think of a situation where that would come in useful.
It would be possible to extend support to &'static mut references, as long as there is the additional constraint that the referenced type is zero sized.
...
Not doing this means:
Relying on static and const items to create 'static references, which won't work in generics.
Empty-array expressions would remain special cased.
It would also not be possible to safely create &'static mut references to zero-sized types, though that part could also be achieved by allowing mutable references to zero-sized types in constants.
So Rust doesn't forbid you from creating &'static mut () actually, just forbids that in static promotion instead: Rust Playground