Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is lifetime elision in very simple terms?

Tags:

rust

lifetime

From the Rust documentation:

Rust supports powerful local type inference in the bodies of functions, but it deliberately does not perform any reasoning about types for item signatures. However, for ergonomic reasons, a very restricted secondary inference algorithm called “lifetime elision” does apply when judging lifetimes. Lifetime elision is concerned solely with inferring lifetime parameters using three easily memorizable and unambiguous rules. This means lifetime elision acts as a shorthand for writing an item signature, while not hiding away the actual types involved as full local inference would if applied to it.

I don't understand what this means. What are item signatures? What does "infer lifetime parameters" mean? Some examples or analogies would be helpful.

like image 285
davis Avatar asked Oct 30 '16 03:10

davis


1 Answers

An item signature is the bit which gives the name and types of your function, i.e. everything you need to call it (without needing to know how it's implemented); for example:

fn foo(x: u32) -> u32;

Here's another which takes a &str reference:

fn bar<'a>(s: &'a str) -> &'a str;

In Rust, all references have an attached lifetime; this is part of the type. The above bar function says more than just "this function takes a reference to a string and returns another one". It says "this function takes a string reference, and returns another which is valid for as long as the one it's given. This is an important part of Rust's ownership system.

However, it's annoying and a pain to specify these lifetimes every time, so Rust has "lifetime elision" (i.e. "not explicitly writing them out"). All that means is that for a few very common cases, you can leave the lifetime annotations out and Rust will implicitly add them for you. This is purely a convenience for programmers so that they don't have to write so many lifetimes in "obvious" cases.

The rules are listed in the book, but for completeness they are:

  1. Every lifetime in the function parameters which isn't otherwise specified is different. For example:
fn f(x: &T, y: &U)

means:

fn f<'a, 'b>(x: &'a T, y: &'b U)

i.e. there's no automatic link between those lifetimes.

  1. If there's only one input lifetime, it's used for every output lifetime. For example:
struct U<'a> {}  // struct with a lifetime parameter

fn f(x: &T) -> &U

becomes:

fn f<'a>(x: &'a T) -> &'a U<'a>
  1. Otherwise, if there are multiple input lifetimes but one of them is &self or &mut self (i.e. it's a method), then all the elided output lifetimes get the same as self. This covers the common case that a method returns a reference to one of its fields. For example:
impl S {
    fn get_my_item(&self, key: &str) -> &str {}
}

becomes:

fn get_my_item<'a,'b>(&'a self, key: &'b str) -> &'a str  // use the self lifetime

The documentation has some more examples.

like image 152
Chris Emerson Avatar answered Sep 20 '22 14:09

Chris Emerson