Lifetimes
TypeScript has no equivalent
Section titled “TypeScript has no equivalent”TypeScript has no concept of lifetimes. When you hold a reference to an object, the garbage collector keeps the object alive as long as the reference exists. You never have to think about whether a reference is still valid.
Rust has no garbage collector, so it must prove — at compile time — that every reference is valid for exactly as long as it is used. This proof system is called lifetimes.
Why lifetimes exist — the dangling reference problem
Section titled “Why lifetimes exist — the dangling reference problem”TypeScript never has dangling references — the GC prevents the underlying data from being freed while any reference exists. Rust frees data when its owner goes out of scope. Without lifetimes, it would be possible to return a reference to data that has already been freed:
// This does NOT compile — and that is the point.fn dangle() -> &String { let s = String::from("hello"); &s // error[E0106]: missing lifetime specifier // But even with a lifetime, this would fail: // s is dropped at the end of this function — the reference would dangle!}// error[E0515]: cannot return reference to local variable `s`// --> src/main.rs:3:5// |// 3 | &s// | ^^ returns a reference to data owned by the current functionThe compiler catches this. There is no equivalent protection in TypeScript because the GC prevents the scenario entirely — but at the cost of a runtime overhead.
Lifetime annotations on functions
Section titled “Lifetime annotations on functions”When a function returns a reference derived from its inputs, the compiler needs to know which input the output’s lifetime is tied to. You annotate this with 'a:
// TypeScript — no lifetime concept neededfunction longest(x: string, y: string): string { return x.length >= y.length ? x : y;}
const result = longest("long string", "xyz");console.log(result); // "long string"// TypeScript never worries about whether the returned string// outlives the inputs. The GC handles it.// 'a is a lifetime parameter — it says:// "the output lives at least as long as BOTH inputs"fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y }}
fn main() { let s1 = String::from("long string"); let result; { let s2 = String::from("xyz"); result = longest(s1.as_str(), s2.as_str()); println!("longest: {}", result); // result must be used before s2 is dropped }}Lifetimes in structs
Section titled “Lifetimes in structs”If a struct holds a reference, it must declare a lifetime to say “the struct cannot outlive the data it references”:
// TypeScript — a struct holding a reference is just an object propertyinterface ImportantExcerpt { part: string; // just a string — GC keeps it alive as long as needed}
const text = "Call me Ishmael. Some years ago...";const excerpt: ImportantExcerpt = { part: text.split(".")[0] };console.log(excerpt.part);// The struct holds a reference, not owned data.// 'a says: the ImportantExcerpt cannot outlive the &str it references.struct ImportantExcerpt<'a> { part: &'a str,}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("no period"); let excerpt = ImportantExcerpt { part: first_sentence }; println!("{}", excerpt.part); // novel is still alive here, so the reference in excerpt is valid}Try it
Section titled “Try it”fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
fn main() { let s1 = String::from("long string"); let result; { let s2 = String::from("xyz"); result = longest(s1.as_str(), s2.as_str()); println!("longest: {}", result); }}Compiling…