Temporary values

Why does the following program compile? Foo(1) is a temporary value, so it should go out of scope after the line where it is created, and x should be a dangling reference after that.

// This program unexpectedly compiles.
struct Foo(i32);

fn f(x: &Foo) ->  &Foo {
    x
}

fn main() {
    let x = f(&Foo(1));
    assert_eq!(x.0, 1);
}

However, if I implement the Drop trait for Foo, then the compiler rejects the program, the expected behavior.

// This program does not compile.
struct Foo(i32);

fn f(x: &Foo) ->  &Foo {
    x
}

impl Drop for Foo {
    fn drop(&mut self) {
        println!("drop");
    }
}

fn main() {
    let x = f(&Foo(1));
    assert_eq!(x.0, 1);
}

Why?

3 Likes

This is thanks to static promotion of const values. The compiler secretly transforms your main function into the following:

fn main() {
    static STATIC: Foo = Foo(1);
    const REF: &'static Foo = &STATIC;

    let x = f(REF);
    assert_eq!(x.0, 1);
}
5 Likes

It seems suboptimal that this case causes static promotion, rather than just promoting to a “hidden” stack slot. Why tie up space in the binary with a static if this could be desugared to a purely stack local. I guess it makes the rules and impl easier?

3 Likes

Perhaps for the same reason that "hello" is &'static and not function-local?

The constant would still have to be stored in the binary to initialize the stack variable, no?

I suppose that depends. In a "perfect" world, the code like OP's would get optimized away entirely, with at most the constant 1 being put into a register. Without optimizations, sure, instructions would need to be present to materialize the value on each call to the function that makes use of it.

Perhaps promoting it to static still leaves room for not actually emitting it into the image, as in it's promoted to static in a moral equivalent sense but not necessarily physically.

Perhaps, although string literals being &'static is rooted in Rust history, whereas static promotion is a relatively new thing :slight_smile:.

I suppose one could even make the argument that a stack local string ought to be subject to the same optimization potential, and maybe it is. I'm not sure how firm the compiler treats the static-ness of string slices (or static promoted rvalues). But I can see a case to be made for scalar replacing a short string slice as well (or even materializing into a stack local buffer), rather than loading from memory. I suppose if we want to be sticklers to the "zero cost abstractions" memo, then this type of thing seems relevant :slight_smile:.

Yup -- the static is anonymous (and thus non-pub), so the optimizer is absolutely free to remove it if it turns out that nothing cares about its static-ness. As a trivial example,

pub fn silly() -> i32 {
    let x: &'static i32 = &1;
    *x
}

optimizes to just

define i32 @_ZN10playground5silly17h0b8fea78c8b9e757E() unnamed_addr #0 {
start:
  ret i32 1
}

With no static left around.

Nor is this exclusive to anonymous statics; the same happens with

pub fn silly() -> i32 {
    static Y: i32 = 1;
    Y
}
1 Like

Yeah, ok - I suppose if the optimizer is aggressive about removing statics whose static-ness is unobservable - and these rvalue promotions should fall into that camp - then all's good :slight_smile:.