Lifetime of object on the stack

I read that in Rust world, every new object is on the stack (if it is not wrapped by Box() or RC()).
But how does it work with an object returned by a function?
Like in below code, user1 is created in function new_user(), and should be on stack of new_user().
When new_user() ends, the stack of new_user() should also be freed. How can user1 still be available in main()?

struct User {
    username: String,
    email: String,
}

fn new_user() -> User {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
    };
    
    return user1;
}

fn main() {
    let user1 = new_user();
    println!("{} {}", user1.email, user1.username);

}
1 Like

The value is moved, which in Rust is a plain copy that invalidates the original location (unless the type is Copy). But you couldn't return a reference to such a value, because as you say, that stack location doesn't exist after you return.

5 Likes

ah, thanks for the explanation, that clears my head a lot.
I was not aware move involves a copy internally.

FWIW, a move doesn't necessarily translate to a copy in terms of actually executed code. That particular example can build the return value in the caller's stack frame to begin with

4 Likes

thanks @garagedragon, noted.
hats off the to rust compiler!

Another thing I found helpful when thinking about moves is that they never copy heap data. (Am I correct?) If you move a Vec, it'll maybe copy the (ptr, len, cap) stack data, but never the heap data which it manages. So a move in itself won't ever be as expensive as allocating a new 1000 byte buffer on the heap and copying data from some other 1000 byte location into it.

The point is that the Rust language doesn't even know what the "heap" is. This allows to run programs written in Rust on environments without heap memory, like the embeded system or the boot sector.

So for the core language semantics, a Vec<T> is a plain struct with 3 fields. To move this struct the compiler generates code to copy those 3 fields, and invalidates previous location at compile time since the Vec<T> doesn't impl Copy

3 Likes

And sometimes the compiler is smart enough to do return value optimization and elide the copy by directly constructing the returned value in its destination on the caller's stack frame?

Yes. And if the function is trivial enough, even the function call itself would be inlined as a whole so it can be literally zero cost.

Many features of the Rust, like iterators, are relied on the optimization to meet its desired performance. Concise language spec or high runtime performance, pick two.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.