[rust-dev] lifetime syntax

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