Skip to content

Commit

Permalink
Copy/move lifetime elision document from other places
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewjasper committed Jan 7, 2018
1 parent 1d791b5 commit 21b4b9f
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 106 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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
180 changes: 180 additions & 0 deletions src/lifetime-elision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# 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 signatures.

A *lifetime position* is anywhere you can write a lifetime in a type:

```rust,ignore
&'a T
&'a mut T
T<'a>
```

Lifetime positions can appear as either "input" or "output":

* For `fn` definitions, input refers to the types of the formal arguments
in the `fn` definition, while output refers to
result types. So `fn foo(s: &str) -> (&str, &str)` has elided one lifetime in
input position and two lifetimes in output position.
Note that the input positions of a `fn` method definition do not
include the lifetimes that occur in the method's `impl` header
(nor lifetimes that occur in the trait header, for a default method).

* In the future, it should be possible to elide `impl` headers in the same manner.

Elision rules are as follows:

* Each elided lifetime in input position becomes a distinct lifetime
parameter.

* If there is exactly one input lifetime position (elided or not), that lifetime
is assigned to *all* elided output lifetimes.

* If there are multiple input lifetime positions, but one of them is `&self` or
`&mut self`, the lifetime of `self` is assigned to *all* elided output lifetimes.

* Otherwise, it is an error to elide an output lifetime.

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
```

## Defaults 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].

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>>
```

## `'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 = ..
```

[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
[elision-nomicon]: ../nomicon/lifetime-elision.html
65 changes: 5 additions & 60 deletions src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,66 +462,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 @@ -587,4 +532,4 @@ The notation `&self` is a shorthand for `self: &Self`.
[auto traits]: special-types-and-traits.html#auto-traits
[object safe]: items/traits.html#object-safety
[issue 47010]: https://github.com/rust-lang/rust/issues/47010
[issue 33140]: https://github.com/rust-lang/rust/issues/33140
[issue 33140]: https://github.com/rust-lang/rust/issues/33140

0 comments on commit 21b4b9f

Please sign in to comment.