Returning a reference to a newly created struct works

Hi,

I was surprised to discover that this code works and I'm not sure why:

struct Bar;
struct Foo {
    bar: Bar,
}

fn new_foo<'a>() -> &'a Foo {
    &Foo { bar: Bar }
}

It looks like a dangling reference, as we just created a Foo and returned a reference to it, but the compiler lets it go so where is the Foo after that call?
Note that understandably, returning something like &Foo::new() doesn't work

It's called "static promotion" (google this expression to learn more). Certain simple expressions (eg. integer literals but also your trivial struct construction) can implicitly be stored in static memory because they are known at compile time, and then taking their address yields a reference that's valid for any lifetime.

5 Likes

Understood, thanks. I wasn't aware that this was possible so I take it as sugar for something like:

fn new_foo<'a>() -> &'a Foo {
    static F: Foo = Foo { bar: Bar }; 
    &F
}
1 Like

Yes, it's something like that.

1 Like

There’s also a single type that supports this for mutable reference: Empty arrays. (In principle, this could be sound with mutable references for any zero-sized type.)

fn empty<'a, T>() -> &'a mut [T; 0] {
    &mut []
}

This can occasionally be useful, especially if you use it as a slice, &mut [T]. On a similar note, &mut [T] implements Default (the implementation uses this feature internally, too), and thus also supports mem::take, powering code such as

struct MySliceIterator<'a, T> {
    the_slice: &'a mut [T],
}

fn iter_mut<T>(the_slice: &mut [T]) -> MySliceIterator<'_, T> {
    MySliceIterator { the_slice }
}

use std::mem;

impl<'a, T> Iterator for MySliceIterator<'a, T> {
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        match mem::take(&mut self.the_slice) {
            [first, rest @ ..] => {
                self.the_slice = rest;
                Some(first)
            }
            [] => None,
        }
    }
}
7 Likes

I am mildly surprised that this doesn't work for syntactically obvious ZSTs like &mut () and struct Foo; &mut Foo.

1 Like

Maybe it should be opt-in though, or disabled by non_exhaustive.

1 Like