
Rust Lifetimes - luu
http://www.charlesetc.com/rust/2015/10/29/
======
dave_ops
This has been my problems with Rust for the last 10-12 months.

Lifetime elision seems thoroughly broken or extremely limited in
functionality, and now when I'm writing (sometimes seemingly trivial)
solutions in Rust I'm spending at least as much time and mental energy
explicitly annotating lifetimes as I would be if I was just managing malloc
and free in C.

As a result the pain vs. benefit curve doesn't bend nearly as far toward Rust
as it theoretically should.

~~~
Animats
That can be a problem. Some common C/C++ idioms do not translate to Rust. In
particular, a function which creates and returns an object is difficult to
express in Rust. In Rust, you have to create the object before the call, then
pass it to a function to be filled in. Either that, or go with a reference-
counted type.

~~~
ryeguy
What makes you think you can't create and return an object? This is how every
constructor function in rust works.

~~~
shadowmint
I believe this is referring to '...and keep a mutable reference to it' ie. A
singleton, and many overseer style observe-and-update-on-change data binding
patterns.

Basically, shared ownership is pretty central to many C++ patterns, which
means they translate poorly into rust.

------
fooyc
Note that the go exemple about returning a pointer to a stack variable is
actually valid in Go: variables that escape their scope are allocated on the
heap.

~~~
steveklabnik
I _think_ the author was just trying to convey that it's no longer stack
allocated in this case. Escape analysis is really nice, but when you need
performance, it can get in the way. I've talked to people who ended up having
to leave a ton of comments with the equivalent of "dont do this, it messes up
escape analysis and performance goes south" all over their codebase.

~~~
Manishearth
I'm actually considering adding escape analysis to _Rust_ (as a part of
clippy, that is) so that we can advise users to convert heap allocated
Vec/Boxes to stack allocated thingies.

~~~
steveklabnik
That would be super cool!

------
jhasse
struct Sheep<'c> { age: &'c i32, }

I don't get why this is needed at all and isn't implicit? What are other
possible values for a lifetime in a struct?

~~~
wycats
As the person who originally pushed extremely hard for all of the lifetime
elision that we have today, I agree with you that this case is still too
verbose.

That said, the reason it wasn't included in the original elision RFC
([https://github.com/rust-lang/rfcs/pull/141](https://github.com/rust-
lang/rfcs/pull/141)) is that the design space here is a little bit more subtle
than some of the cases that _did_ make it.

In particular, I believe that it's important to know that a particular struct
contains a borrowed value somewhere.

So let's say you could leave off the `'c` on the Sheep struct, and I wrote a
struct containing `Sheep`:

    
    
        struct Flock {
            sheep: Vec<Sheep>
        }
    

Now, we have visually lost the fact that a borrowed value is stored inside the
flock. Since data structures can get very recursive, this rapidly causes
useful information about ownership to get lost.

What I'd like to see personally is:

    
    
        struct Sheep<&> {
            age: &i32
        }
    
        struct Flock<&> {
            sheep: Vec<Sheep>
        }
    

What this would mean is that you can always see, by looking at a struct
definition, whether it contains borrowed values (anywhere recursively), but
without the grotty need to define and wire up lifetime names.

I like the fact that this maintains the strong mental notion of ownership (the
meaning of the & "operator" in Rust), but, as with normal elision, it
eliminates the wiring that a good compiler should be able to do for you.

~~~
dan00
There're even people that don't like the automatic moving in Rust, because
they can't see where something is moved.

I can understand why people feel that way, but it's a bit irrational, because
if automatic behaviour can't result into incorrect code, more ressource usage
or higher work load, why should I care about?

I have pretty much the same feeling about explicit lifetimes in this example.

Again, I understand that people want the whole control, want to see every
relevant information, but everything has its costs, and the relevant
information in each use case isn't always the same.

~~~
steveklabnik
We did try making moves and copies into distinct syntaxes.

    
    
      let x = foo; // <- copies
      let x = move foo; // <- moves
    

It was worse, or at least, was by the majority of people at the time. See
[http://smallcultfollowing.com/babysteps/blog/2012/10/01/move...](http://smallcultfollowing.com/babysteps/blog/2012/10/01/moves-
based-on-type/) for more of this ancient history.

------
shadowmint
cool post. what about traits and 'static?

Anyhow; Its definitely worth making two points about lifetimes very clear:

1) They don't exist at runtime.

Lifetimes are purely a compile time thing the compiler uses for formal safety.

2) 'a is not a actual lifetime, it is the minimum possible life time valid for
that item; ie. a _bound_ , not a concrete value.

&'a means; this arg/trait/ref/whatever must live at least as long as 'a.
That's why different objects with different lifetimes can coexist in a struct
of 'a, without needing 'a, 'b, 'c etc for each reference in the struct.

~~~
dbaupp
It's not clear from context in your comment if you're saying this, but, for
`x: &'a T`, 'a is a bound in two ways:

\- it is the maximum possible lifetime of the x variable itself,

\- it is the minimum lifetime of the `T` instance x points to.

These are 100% related of course (x can't point to something that is invalid),
but it is worth being careful about which end of a reference/pointer you're
talking about when talking about validity.

~~~
sixbrx
Thanks for this comment, that clarifies things a lot for me. I think it should
be in the docs (with equal brevity) if not already.

~~~
steveklabnik
I've made a note to check it out, thanks.

------
seivadmas

      Rust is a unique language in that it deallocates memory on the heap
      without requiring the writer to call free,
      while at the same time having no need for a garbage collector.
    

Someone correct me if I'm wrong but surely this is not unique - don't
Objective C and Swift have automatic reference counting which accomplishes
much the same thing?

~~~
Nyetan
Intersection ARC, Lifetimes:

* Not having to worry about trivial memory/resource allocations

ARC - Lifetimes:

* Can blow up hilariously with refcount cycles

* Incurs a runtime overhead

Lifetimes - ARC:

* For the subset of allocation patterns it handles, it does so with zero runtime overhead (i.e. invalid programs do not compile, invalid = leaks memory)

* Use extends beyond just memory allocation/deallocation (see the concurrency docs [1] for example) (note that Mozilla wrote Rust to help write Servo [2], their prototype highly-parallel browser engine, so this use case was a target from the getgo)

* Cannot encode all data structures a GC can (hence why reference counting is part of the stdlib [3])

[1]: [https://doc.rust-lang.org/book/concurrency.html](https://doc.rust-
lang.org/book/concurrency.html)

[2]: [https://github.com/servo/servo](https://github.com/servo/servo)

[3]: [https://doc.rust-lang.org/alloc/rc/](https://doc.rust-
lang.org/alloc/rc/)

~~~
Rusky
> invalid = leaks memory

Not quite true- invalid use of lifetimes typically means use-after-free rather
than a memory leak (and if you include the standard library, then you can leak
memory in safe Rust).

~~~
tatterdemalion
Its interesting to me how everyone goes to "memory leaks" as their example of
memory unsafety, and then if we're talking about Rust we need to clarify that
Rust allows leaks (but it makes them hard to do by accident). I guess
mnemonically "memory leak" is easier to bring to mind than "dangling pointer"
when talking about memory.

~~~
Gankro
It's also funny because memory leaks literally aren't a memory safety issue.
If they were, every garbage collected language would be memory-unsafe.

------
Manishearth
[This comment is outdated, I discussed this with the author and he improved
the post :)]

I feel this post confuses two different notions of lifetime. And also mixes
concrete lifetimes with lifetime parameters.

There's the CS notion of liveness, and then there's the lifetime notation in
Rust.

> To know when to deallocate objects

This is handled by the CS notion of liveness. Well, an approximation to it.
"Things are live until they are used, or until they can no longer _be_ used".

The deallocation comes from Rust's affine type system.

In fact, Box<T> and other self-deallocating things don't have an associated
"lifetime" in the Rust sense of the word, at least not one that dictates their
deallocation.

The lifetime syntax is basically a form of generics which lets one put
constraints on the allowed liveness of &-pointers and their ilk.

> example_function<'a> names the lifetime defined by this function, called 'a.

This isn't true. There does exist an anonymous inner lifetime for the scope of
the function. It's not `'a`

What's happening here is that we're saying, "For _any_ lifetime `'a`, we can
have a version of example_function which returns a pointer which can live that
long.". This doesn't compile, because the compiler knows that this function
can only return pointers which live as long as its scope or less, not "any"
lifetime. (Which is impossible to return from anyway).

This becomes clearer when you have a function like `fn foo<'a> (x: &'a [u8])
-> &'a u8 { &x[0] }` (FWIW if you omit the lifetimes Rust will elide them and
internally produce the same function). Here, what we're saying is, "For any
lifetime 'a, foo can take a slice that lives as long as 'a and return a
pointer that lives as long as 'a". This compiles, because the compiler can see
that a reference to x will live as long as x itself.

Similarly, with structs:

> Sheep<'c> instead of Sheep > > This is not doing anything concrete. It's
> just giving a name to the lifetime that Sheep structs can last for.

This is wrong, too. It's giving a _parameter_ to Sheep, saying "Every Sheep
has an associated lifetime 'c, which is the lifetime of its inner reference".

In fact, it is possible to define the body of sheep in such a way that Sheep
is guaranteed to live _longer_ than `'c` (instead of shorter, as in the
example above). This is explained in the Rustonomicon ([http://doc.rust-
lang.org/stable/nomicon/subtyping.html](http://doc.rust-
lang.org/stable/nomicon/subtyping.html)). But it's an advanced topic, doesn't
crop up often in actual Rust programming.

~~~
dbaupp
_> Here, what we're saying is, "For any lifetime 'a, foo can take a slice that
lives as long as 'a and return a pointer that lives as long as 'a"._

Another way to phrase it is: the return value of the function is guaranteed to
be valid for at least as long as the argument (but may borrow from it, and
hence mutations of the argument need to be outlawed as long as the return
value is held).

~~~
Manishearth
Agreed, it's nicer to talk about "validity" than "liveness" :)

~~~
dbaupp
Well, my rephrasing was meant to be more than just s/validity/liveness/: I
find that removing quantifiers (like for-all) often improves my understanding,
as it usually gives more obvious context/motivation for what the goal is. It
is then easier to understand the more formal version once I know approximately
what it is trying to do.

------
orbitur
Can someone explain what happened in the C example?

~~~
steveklabnik
Well, it's not guaranteed to print 3, it can do anything it wants. Since you
have a dangling pointer, it's not pointing to valid memory. So it depends.

