liveBook · Manning

Lifetime annotation

As explained in Refactoring to Rust

A lifetime annotation is Rust syntax that names or relates the validity periods of references in types and function signatures.

A lifetime annotation names a relationship between borrowed references. Rust can often infer that a returned reference comes from an input. When a function accepts more than one reference, the signature may need to say which input the result borrows from. An annotation describes that relationship; it does not keep the underlying value alive longer. section 4.4.1

Which input owns the result?

`fn get_value(numbers: &Vec<i32>) -> &i32` can return `&numbers[0]`: the single borrowed input makes the relationship clear. Add a separate `s: &str` argument, and Rust cannot tell from the signature whether the returned `&i32` borrows from `numbers` or `s`. Write `fn get_value<'a>(numbers: &'a Vec<i32>, s: &str) -> &'a i32`. The name `'a` ties the result to `numbers`; `s` need not share that lifetime. section 4.4.1

The relationship has a practical consequence. If `value` still refers to an element of `numbers`, Rust rejects a `numbers.push(6)` before the last use of `value`: mutation could invalidate the borrowed element. An annotation cannot make a reference to a function’s local variable safe to return, because that variable does not survive the call. section 4.4.1

Listing 4.13 Returning a reference to an argument
fn main() {
  let numbers = vec![1, 2, 3, 4, 5];

  let value = get_value(&numbers, "Getting the number");

  println!("value: {}", value);
}

fn get_value<'a>(numbers: &'a Vec<i32>, s: &str) -> &'a i32 {  #1
  println!("{}", s);
  &numbers[0]
}

Compare the annotated `numbers` argument and return type with the unannotated `s` argument. section 4.4.1

The same pattern matters when reading an NGINX request body through Rust’s FFI. In `request_body_as_str<'a>(request: &'a ngx_http_request_t) -> Result<&'a str, &'static str>`, the successful string slice borrows request-owned memory and cannot outlive the request reference. The `'static` annotation applies to the error string. The body bytes are viewed as a slice and checked as UTF-8 rather than copied into a new string. This lifetime relationship does not replace the unsafe-code checks needed before reading NGINX pointers. section 4.4 section 4.4.2

unsafe fn request_body_as_str<'a>(
  request: &'a ngx_http_request_t,
) -> Result<&'a str, &'static str> {
  if request.request_body.is_null()
    || (*request.request_body).bufs.is_null()
    || (*(*request.request_body).bufs).buf.is_null()
  {
    return Err("Request body buffers
      were not initialized as expected");
  }

  let buf = (*(*request.request_body).bufs).buf;

  let start = (*buf).pos;
  let len = (*buf).last.offset_from(start) as usize;

  let body_bytes = std::slice::from_raw_parts(start, len);

  let body_str = std::str::from_utf8(body_bytes)
    .map_err(|_| "Body contains invalid UTF-8")?;

  Ok(body_str)
}

Follow the null checks, raw buffer view, UTF-8 conversion, and annotated return type in the complete request-body helper. section 4.4

How Refactoring to Rust covers it

How deep it goes
Works through inferred and explicit signature relationships and borrow-checker errors, then applies them to an unsafe FFI helper.
Examples
Uses a vector-element helper, a rejected vector mutation, and an NGINX request-body conversion.
What it assumes
Builds from small Rust borrowing examples before using annotations with raw pointers in an NGINX handler.
What you can do after
Apply it via Rust function signatures to relate a returned reference to the input whose memory it views.
How it connects
Connects borrowing, reference lifetimes, mutable vector access, string slices, and Rust FFI.

Also explained in

Compare how 2 books explain lifetime annotation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Refactoring to Rust. The section links point to the parts of the book it draws on.