[rust-dev] lifetime syntax

Niko Matsakis niko at alum.mit.edu
Tue Feb 5 10:50:54 PST 2013


We are reaching a degree of consensus on the new lifetime syntax.

The general idea is as follows.

# Lifetime names and borrowed pointers

First, `'lt` is the new lexical class for lifetime names.  A borrowed 
pointer has the (fully specified) form `&'lt mq Type`, where `'lt` is 
the lifetime and `mq` is the mutability (for slices, the syntax is `&'lt 
mq [T]` and `&'lt str`).  As today, `mq` can be `mut` for mutable or 
nothing for immutable (I believe `const` for read-only is deprecated and 
hopefully going away).  So some examples:

&'lt int
&'lt mut int

In some cases, the lifetime may be omitted:

- In a function declaration: `&int` is short for a fresh lifetime name 
(meaning a different name from all the other names in scope)
- In a function body: `&int` is short for an inferred lifetime

# Lifetime parameters and fn/struct declarations

When you define a function or type, it can be lifetime parameterized.  
Lifetime parameters are declared within `<>` just like type parameters.  
If there are both lifetime and type parameters, lifetime parameters must 
appear first.  For example, here is a simple iterator type that iterates 
over a slice.  I have written all lifetime parameters in full, later we 
will see that not all of them are necessary.  (This example compiles 
today, albeit with different syntax)

     struct Iterator<'lt, T> {
         source: &'lt [T],
         index: uint
     }

     fn has_next<'a, 'b, T>(iter: &'a Iterator<'b, T>) -> bool {
         iter.index + 1 < iter.source.len()
     }

     fn next<'a, 'b, T>(iter: &'a mut Iterator<'b, T>) -> Option<&'b T> {
         iter.index += 1;
         if iter.index < iter.source.len() {
             Some(&iter.source[iter.index])
         } else {
             None
         }
     }

When defining a type, all lifetime parameters must be specified in full 
(as shown).  When defining a function, lifetime parameters may be 
omitted if you simply with to use a fresh lifetime.  The fn 
`has_next()`, for example, can be simplified to:

     fn has_next<T>(iter: &Iterator<T>) -> bool { /* same as before */ }

This is exactly equivalent.  Note that we omitted both the lifetime `'a` 
that defined the lifetime of the pointer `iter` and the lifetime `'b` 
which defined the lifetime of `iter.source`.  Because this is part of a 
fn signature, the default for omitted lifetimes is to substitute fresh 
lifetimes.  If we had written the type `&Iterator<T>` inside the fn 
body, the default would be to infer appropriate lifetimes.  Anywhere 
else where we might write that type, for example as part of a struct or 
type declaration, there are no defaults and everything must be specified 
in full.

The fn `next()` can be simplified as well, but not *quite* as much:

     fn next<'b, T>(iter: &mut Iterator<'b, T>) -> Option<&'b T> { /* 
same as before */ }

Here the named lifetime parameter `'b` must remain.  This is because it 
is needed to link the lifetime parameter of the input iterator to the 
returned option.  Note that the lifetime of the pointer `iter` itself is 
not important: the only important thing is the lifetime of the vector 
`iter.source`, which is `'b`.

# Lifetime parameters andimpls

This is what we were discussing in the method but we were cut off.  If 
we wanted to convert the two iterator functions to an impl, we could so 
as follows (again, we begin with the *fully explicit* form):

     impl<'b, T> Iterator<'b, T> {
         fn has_next<'a, T>(&'a self) { /* same as before */ }
         fn next<'a, T>(&'a mut self) -> Option<&'b T> { /* same as 
before */ }
     }

As before, lifetimes can be omitted in method declarations if they are 
not used anywhere else, so this can be simplified to:

     impl<'b, T> Iterator<'b, T> {
         fn has_next<T>(&self) { /* same as before */ }
         fn next<T>(&mut self) -> Option<&'b T> { /* same as before */ }
     }

This is *slightly* different than today.  This is because, today, 
`&self` is equivalent to a self type of `&'b Iterator<'b, T>` rather 
than using a fresh lifetime (like `&'a Iterator<'a, T>`).  The current 
system causes various issues in real-life examples, including this 
specific iterator example.

# Multiple lifetime parameters

Note that this proposal easily permits having multiple lifetime 
parameters on a given struct.  One unclear point is whether you should 
be able to specify only one of many lifetime parameters.  I am inclined 
to say no, it's all or nothing.  For example, given this struct:

    struct Foo<'a, 'b>

You can write `Foo` or `Foo<'lt1, 'lt2>` but not `Foo<'lt1>`.

# Labeling blocks and expressions

Looking to the future, I personally would like to, further out, support 
lifetime names as labels on blocks or expressions, so that you could 
write something like:

     'a: { let x: &'a T = ...; }

Here `'a` would be a name for the block and it could be used as a 
lifetime in type annotations and so forth within the method.  This would 
help in allowing users to write explicit annotations.  It also helps for 
breaking/looping in named loops etc:

     'a: while cond1 {
        'b: while cond2 {
            ...
            break 'a;
         }
     }



Niko


More information about the Rust-dev mailing list