Skip to content

Commit

Permalink
Merge pull request #187 from matthewjasper/lifetime-elision
Browse files Browse the repository at this point in the history
Lifetime elision
  • Loading branch information
alercah authored Mar 3, 2018
2 parents 267cdd8 + 1014b0d commit d0921d9
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 107 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- [Subtyping](subtyping.md)
- [Type coercions](type-coercions.md)
- [Destructors](destructors.md)
- [Lifetime elision](lifetime-elision.md)

- [Special types and traits](special-types-and-traits.md)

Expand Down
2 changes: 1 addition & 1 deletion src/items/constant-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn create_and_drop_zero_with_destructor() {
```

[constant value]: expressions.html#constant-expressions
[static lifetime elision]: items/static-items.html#static-lifetime-elision
[static lifetime elision]: items/lifetime-elision.html#static-lifetime-elision
[`Drop`]: special-types-and-traits.html#drop
[IDENTIFIER]: identifiers.html
[_Type_]: types.html
Expand Down
45 changes: 0 additions & 45 deletions src/items/static-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,50 +59,6 @@ unsafe fn bump_levels_unsafe2() -> u32 {
Mutable statics have the same restrictions as normal statics, except that the
type does not have to implement the `Sync` trait.

## `'static` lifetime elision

Both constant and static declarations of reference types have *implicit*
`'static` lifetimes unless an explicit lifetime is specified. As such, the
constant declarations involving `'static` above may be written without the
lifetimes. Returning to our previous example:

```rust
const BIT1: u32 = 1 << 0;
const BIT2: u32 = 1 << 1;

const BITS: [u32; 2] = [BIT1, BIT2];
const STRING: &str = "bitstring";

struct BitsNStrings<'a> {
mybits: [u32; 2],
mystring: &'a str,
}

const BITS_N_STRINGS: BitsNStrings = BitsNStrings {
mybits: BITS,
mystring: STRING,
};
```

Note that if the `static` or `const` items include function or closure
references, which themselves include references, the compiler will first try
the standard elision rules ([see discussion in the nomicon][elision-nomicon]).
If it is unable to resolve the lifetimes by its usual rules, it will default to
using the `'static` lifetime. By way of example:

```rust,ignore
// Resolved as `fn<'a>(&'a str) -> &'a str`.
const RESOLVED_SINGLE: fn(&str) -> &str = ..
// Resolved as `Fn<'a, 'b, 'c>(&'a Foo, &'b Bar, &'c Baz) -> usize`.
const RESOLVED_MULTIPLE: Fn(&Foo, &Bar, &Baz) -> usize = ..
// There is insufficient information to bound the return reference lifetime
// relative to the argument lifetimes, so the signature is resolved as
// `Fn(&'static Foo, &'static Bar) -> &'static Baz`.
const RESOLVED_STATIC: Fn(&Foo, &Bar) -> &Baz = ..
```

## Using Statics or Consts

In can be confusing whether or not you should use a constant item or a static
Expand All @@ -118,4 +74,3 @@ following are true:
[IDENTIFIER]: identifiers.html
[_Type_]: types.html
[_Expression_]: expressions.html
[elision-nomicon]: ../nomicon/lifetime-elision.html
171 changes: 171 additions & 0 deletions src/lifetime-elision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Lifetime elision

Rust has rules that allow lifetimes to be elided in various places where the
compiler can infer a sensible default choice.

## Lifetime elision in functions

In order to make common patterns more ergonomic, Rust allows lifetimes to be
*elided* in [function item], [function pointer] and [closure trait] signatures.
The following rules are used to infer lifetime parameters for elided lifetimes.
It is an error to elide lifetime parameters that cannot be inferred.

* Each elided lifetime in the parameters becomes a distinct lifetime parameter.
* If there is exactly one lifetime used in the parameters (elided or not), that
lifetime is assigned to *all* elided output lifetimes.

In method signatures there is another rule

* If the receiver has type `&Self` or `&mut Self`, then the lifetime of that
reference to `Self` is assigned to all elided output lifetime parameters.

Examples:

```rust,ignore
fn print(s: &str); // elided
fn print<'a>(s: &'a str); // expanded
fn debug(lvl: usize, s: &str); // elided
fn debug<'a>(lvl: usize, s: &'a str); // expanded
fn substr(s: &str, until: usize) -> &str; // elided
fn substr<'a>(s: &'a str, until: usize) -> &'a str; // expanded
fn get_str() -> &str; // ILLEGAL
fn frob(s: &str, t: &str) -> &str; // ILLEGAL
fn get_mut(&mut self) -> &mut T; // elided
fn get_mut<'a>(&'a mut self) -> &'a mut T; // expanded
fn args<T: ToCStr>(&mut self, args: &[T]) -> &mut Command; // elided
fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // expanded
fn new(buf: &mut [u8]) -> BufWriter; // elided
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // expanded
type FunPtr = fn(&str) -> &str; // elided
type FunPtr = for<'a> fn(&'a str) -> &'a str; // expanded
type FunTrait = Fn(&str) -> &str; // elided
type FunTrait = for<'a> Fn(&'a str) -> &'a str; // expanded
```

## Default trait object lifetimes

The assumed lifetime of references held by a [trait object] is called its
_default object lifetime bound_. These were defined in [RFC 599] and amended in
[RFC 1156]. Default object lifetime bounds are used instead of the lifetime
parameter elision rules defined above.

If the trait object is used as a type argument of a generic type then the
containing type is first used to try to infer a bound.

* If there is a unique bound from the containing type then that is the default
* If there is more than one bound from the containing type then an explicit
bound must be specified

If neither of those rules apply, then the bounds on the trait are used:

* If the trait is defined with a single lifetime _bound_ then that bound is
used.
* If `'static` is used for any lifetime bound then `'static` is used.
* If the trait has no lifetime bounds, then the lifetime is inferred in
expressions and is `'static` outside of expressions.

```rust,ignore
// For the following trait...
trait Foo { }
// These two are the same as Box<T> has no lifetime bound on T
Box<Foo>
Box<Foo + 'static>
// ...and so are these:
impl Foo {}
impl Foo + 'static {}
// ...so are these, because &'a T requires T: 'a
&'a Foo
&'a (Foo + 'a)
// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
std::cell::Ref<'a, Foo>
std::cell::Ref<'a, Foo + 'a>
// This is an error:
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b>
TwoBounds<'a, 'b, Foo> // Error: the lifetime bound for this object type cannot
// be deduced from context
```

Note that the innermost object sets the bound, so `&'a Box<Foo>` is still `&'a
Box<Foo + 'static>`.

```rust,ignore
// For the following trait...
trait Bar<'a>: 'a { }
// ...these two are the same:
Box<Bar<'a>>
Box<Bar<'a> + 'a>
// ...and so are these:
impl<'a> Foo<'a> {}
impl<'a> Foo<'a> + 'a {}
// This is still an error:
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b>
TwoBounds<'a, 'b, Foo<'c>>
```

## `'static` lifetime elision

Both [constant] and [static] declarations of reference types have *implicit*
`'static` lifetimes unless an explicit lifetime is specified. As such, the
constant declarations involving `'static` above may be written without the
lifetimes.

```rust
// STRING: &'static str
const STRING: &str = "bitstring";

struct BitsNStrings<'a> {
mybits: [u32; 2],
mystring: &'a str,
}

// BITS_N_STRINGS: BitsNStrings<'static>
const BITS_N_STRINGS: BitsNStrings = BitsNStrings {
mybits: [1, 2],
mystring: STRING,
};
```

Note that if the `static` or `const` items include function or closure
references, which themselves include references, the compiler will first try
the standard elision rules. If it is unable to resolve the lifetimes by its
usual rules, then it will error. By way of example:

```rust,ignore
// Resolved as `fn<'a>(&'a str) -> &'a str`.
const RESOLVED_SINGLE: fn(&str) -> &str = ..
// Resolved as `Fn<'a, 'b, 'c>(&'a Foo, &'b Bar, &'c Baz) -> usize`.
const RESOLVED_MULTIPLE: &Fn(&Foo, &Bar, &Baz) -> usize = ..
// There is insufficient information to bound the return reference lifetime
// relative to the argument lifetimes, so this is an error.
const RESOLVED_STATIC: &Fn(&Foo, &Bar) -> &Baz = ..
```

[closure trait]: types.html#closure-types
[constant]: items.html#constant-items
[function item]: types.html#function-item-types
[function pointer]: types.html#function-pointers
[implementation]: items/implementations.html
[RFC 599]: https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md
[RFC 1156]: https://github.com/rust-lang/rfcs/blob/master/text/1156-adjust-default-object-bounds.md
[static]: items.html#static-items
[trait object]: types.html#trait-objects
[type aliases]: items/type-aliases.html
65 changes: 4 additions & 61 deletions src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,66 +567,11 @@ type signature of `print`, and the cast expression in `main`.
### Trait Object Lifetime Bounds

Since a trait object can contain references, the lifetimes of those references
need to be expressed as part of the trait object. The assumed lifetime of
references held by a trait object is called its *default object lifetime bound*.
These were defined in [RFC 599] and amended in [RFC 1156].
need to be expressed as part of the trait object. This lifetime is written as
`Trait + 'a`. There are [defaults] that allow this lifetime to usually be
infered with a sensible choice.

For traits that themselves have no lifetime parameters:
* If there is a unique bound from the containing type then that is the default.
* If there is more than one bound from the containing type then an explicit
bound must be specified.
* Otherwise the default bound is `'static`.

```rust,ignore
// For the following trait...
trait Foo { }
// These two are the same as Box<T> has no lifetime bound on T
Box<Foo>
Box<Foo + 'static>
// ...and so are these:
impl Foo {}
impl Foo + 'static {}
// ...so are these, because &'a T requires T: 'a
&'a Foo
&'a (Foo + 'a)
// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
std::cell::Ref<'a, Foo>
std::cell::Ref<'a, Foo + 'a>
// This is an error:
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b>
TwoBounds<'a, 'b, Foo> // Error: the lifetime bound for this object type cannot
// be deduced from context
```

The `+ 'static` and `+ 'a` refer to the default bounds of those kinds of trait
objects, and also to how you can directly override them. Note that the innermost
object sets the bound, so `&'a Box<Foo>` is still `&'a Box<Foo + 'static>`.

For traits that have a single lifetime _bound_ of their own then, instead of
infering 'static as the default bound, the bound on the trait is used instead

```rust,ignore
// For the following trait...
trait Bar<'a>: 'a { }
// ...these two are the same:
Box<Bar<'a>>
Box<Bar<'a> + 'a>
// ...and so are these:
impl<'a> Foo<'a> {}
impl<'a> Foo<'a> + 'a {}
// This is still an error:
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b>
TwoBounds<'a, 'b, Foo<'c>>
```
[defaults]: lifetime-elision.html#elision-and-defaults-in-trait-objects

## Type parameters

Expand Down Expand Up @@ -693,8 +638,6 @@ impl Printable for String {
[`Vec<T>`]: ../std/vec/struct.Vec.html
[dynamically sized type]: dynamically-sized-types.html
[dynamically sized types]: dynamically-sized-types.html
[RFC 599]: https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md
[RFC 1156]: https://github.com/rust-lang/rfcs/blob/master/text/1156-adjust-default-object-bounds.md
[struct expression]: expressions/struct-expr.html
[closure expression]: expressions/closure-expr.html
[auto traits]: special-types-and-traits.html#auto-traits
Expand Down

0 comments on commit d0921d9

Please sign in to comment.