Why can't `&mut ()` be 'static?

The following is valid Rust:

fn foo<'a>() -> &'a mut [u8] {
    &mut []
}

I presume that this is valid because [] has zero length, so no actual memory region needs to exist in order for the reference to be valid. Why, then, is the following code not valid for the same reason?

fn foo<'a>() -> &'a () {
    &mut ()
}
1 Like

Basically, you can't promote something to a mutable static generally, but [] is special-cased for historical reasons. Extending the behavior to all ZSTs was considered. You have to be explicit about being static for mutable non-ZSTs, versus relying on promotion. (Mutable statics are unsafe generally.)

(I suspect you're aware that for non-mutable references, the promotion works -- and works for non-ZSTs as well. More information in that RFC.)

6 Likes

What's the point of constructing a &mut ()?

I mean, replacing the value means putting the identical value in its place, and the difference cannot even be detected after such a switch. So what can it be used for?

1 Like

I'm using it in a generic context roughly analogous to:

// defined in someone else's crate
pub trait Foo {
    type State;
    type A;
    type B;

    fn get_a(state: &mut Self::State) -> &mut Self::A;
    fn get_b(state: &mut Self::State) -> &mut Self::B;
}

If I want to set A or B to (), then in order to implement get_a or get_b, I need to be able to write &mut ().

2 Likes

Awesome, thanks for the context! Time for another doc rabbit hole :smiley:

Having encountered a similar problem while working with allocators, I'm using the following approach of proof instances. The one benefit is that this extends to any allocation-like use and not only the &mut _ special case. So, for example, Box<[u8]> can be fitted in. The code only has the relevant portion:

/// This type is inhabited only for zero-sized `T`.
///
/// The property is ensured by offering only one method of construction:
/// Copying from the associated Singleton instance `IS`. This constant
/// however requires the size to evaluate to `0` in its definition, else
/// the use errors (`deny(const_err)`).
pub struct Zst<T>(PhantomData<T>);

impl<T> Zst<T> {
    pub const IS: Self = [Zst(PhantomData)][size_of::<T>()];
    
    pub fn leak<'a>(self, val: T) -> &'a mut T {
        let mut location = ptr::NonNull::<T>::dangling();
        core::mem::forget(val);
        // SAFETY:
        // * properly aligned as per dangling
        // * dereferencable as T is a ZST
        // * initialized as T is a ZST
        // * non-aliased as T is a ZST
        unsafe { location.as_mut() }
    }
}

impl<T> Clone for Zst<T> {
    fn clone(&self) -> Self { *self }
}

impl<T> Copy for Zst<T> {}

(License: do what you want with it)

5 Likes

FWIW, with ::alloc, this can be achieved without unsafe thanks to Box::leak(Box::new(()))

4 Likes