🎓 A1Learner
🔍

Rust Subtyping

Subtyping lets one type stand in for another type. A longer lifetime is a subtype of a shorter lifetime.

A longer promise fills a shorter slot

hello is promised for the whole program. Storing it in a variable that only asks for a shorter promise is fine — the promise shrinks to fit:

let hello: &'static str = "hello";
let shorter: &str = hello;
println!("shorter = {}", shorter);

Two lifetimes meet in one call

show_both takes two references under one shared lifetime. The literal brings the longest lifetime there is, the local String the shortest — Rust shrinks the longer one to match:

fn show_both(a: &str, b: &str) {
    println!("a = {} b = {}", a, b);
}
let motto: &'static str = "stay";
let local = String::from("go");
show_both(motto, &local);

The shrink passes through a Box

A Box holding a &'static str can be handed over as a Box holding a shorter &str. Forgetting the longer promise is safe: the value itself has not changed, only what is promised about it:

let boxed: Box<&'static str> = Box::new("kept for good");
let shorter: Box<&str> = boxed;
println!("boxed = {}", shorter);

Stretching a short promise is refused

The other direction is the dangerous one. needs_static demands text that lives for the whole program, and a local String cannot promise that:

fn needs_static(s: &'static str) { println!("{}", s); }
let local = String::from("gone soon");
needs_static(&local); // error E0597: `local` does not live long enough

Example

fn announce(a: &str, b: &str) {
    println!("{} and {}", a, b);
}
fn motto() -> &'static str {
    "borrowed for good"
}
fn main() {
    let local = String::from("gone soon");
    announce(motto(), &local);
    let calmer: &str = motto();
    println!("calmer = {}", calmer);
    println!("motto again = {}", motto());
}

Key points

  • Subtyping: one type may be used in place of another.
  • It must meet every promise the other makes.
  • The swap is silent, with no cast and no copy.
  • 'static is a subtype of every lifetime.
  • A longer stretch of validity contains every shorter one.
  • It is written 'long <: 'short.
  • It is read "'long is a subtype of 'short".
  • A &'static str can stand in for any &'a str — the reference simply shrinks to the shorter lifetime
  • The shrink is one-way.
  • A short lifetime can never be stretched into a longer one.
  • Code that tries fails with error E0597.
  • Use this idea to read errors.
  • When Rust says a value "does not live long enough", it found a mismatch.
  • Only a shrink was allowed in that place.
  • A stretch was attempted instead.

In this example, motto hands back a &'static str: text that lives for the whole program. announce expects two references under one ordinary lifetime. So the 'static promise shrinks. It sits beside the borrow of local, a String that dies at the end of main. The first line prints borrowed for good and gone soon. Below, calmer asks for a plain &str and receives the same 'static text, shrunk again. Nothing was copied and nothing was cast: the longer promise simply stood in for a shorter one, three times.

Note

Outside lifetimes, Rust has almost no subtyping.

Two different struct types are never subtypes of each other — there is no inheritance in Rust.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why may a &'static str be passed where a &str with a shorter lifetime is expected?