
Static Lifetimes and Factories
· 4 minutes reading time Development rust
I’m just getting used to the Rust borrow checker, and I feel pretty good about it. I’ve learned that values cannot outlive the variables they’re bound to, that the compiler prevents me from accessing data that has been dropped, and that lifetimes ensure that references remain valid.
So I write something like this:
fn main() {
let a = "hello";
let c: &str;
{
let b = "world!";
c = choose(&a, &b);
}
println!("c: {}", c); // <-- I was expecting a compile-time error here
}
// Return a reference to the longer slice
fn choose<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() >= s2.len() { s1 } else { s2 }
}
At first glance, I was confident: b
lives in a smaller inner scope, so clearly it cannot be used after it is dropped.
And choose
might return &b
, so this should not compile.
Wrong. It does compile.
But why? Because "world!"
is a string literal, which has a special property in Rust: it has a 'static
lifetime.
This means it lives for the entire duration of the program. It’s not just some stack value, but it’s embedded
directly in the binary.
So when you write:
let b = "world!"; // type: &'static str
You’re assigning a reference that’s already 'static
, even though b
the variable itself goes out of scope. So
choose(&a, &b)
returns a &'static str
, and c
safely points to it, even outside the block where b
is defined.
Let’s produce the expected compile-time error
Change just one thing:
fn main() {
let a = String::from("hello");
let c: &str;
{
let b = String::from("world!");
c = choose(&a, &b);
}
println!("c: {}", c); // <-- We get a compile-time error here
}
Now it fails to compile. String::from("world!")
creates a heap-allocated string, and &b
is only valid as long as
b
is in scope. The compiler doesn’t allow a reference to b
to escape its block.
This is exactly what we would expect, but the first example violates that expectation precisely because of the 'static
literal.
Building a factory with 'static
references
Let’s try something similar to the earlier scope example. What if I allocate a string inside a block, but want to use it safely outside?
This would usually fail:
fn main() {
let s: &str;
{
let name = String::from("Rust");
s = name.as_str(); // <-- name is dropped here
}
println!("{}", s); // <-- ERROR: use of borrowed value after move
}
Now here’s the twist. If I replace the heap allocation with a leaked one, it compiles and runs:
fn main() {
let s: &'static str;
{
let name = Box::leak("Rust".to_string().into_boxed_str());
s = name;
}
println!("{}", s); // <-- Works fine
}
It looks like name
goes out of scope, but since we’ve leaked the box, the string is never freed. s
holds a 'static
reference to the leaked memory, and the compiler is happy.
Box::leak
turns a heap allocation into a 'static
reference by intentionally leaking memory. Rust will never free
that memory. The box is consumed, and only a reference is returned.
But can I drop it later?
No. Once you leak memory with Box::leak
, it’s yours forever. You can’t drop()
the value anymore, because you’ve
lost ownership. All you have is a reference, and references don’t manage memory.
If you need reclaimable memory, consider using an arena allocator or keep the Box<T>
somewhere you can access it later
and manually free it.
I learned something
What started with the expectation “This should fail to compile, but doesn’t” turned out to be a lesson in the subtleties of Rust’s lifetime system:
- String literals and other constants live
'static
, and sometimes trick you into thinking scopes matter more than they do. - Heap-allocated values (like
String
) behave differently. They won’t let you sneak references across lifetimes. Box::leak
is a powerful but risky tool: it gives you'static
references to dynamic values, but at the cost of memory you can never get back.
So yes, you can write factory functions that return 'static
references. But do you really need to? Most of the time,
borrowing or owning values explicitly is safer and more idiomatic, and your memory won’t mysteriously grow over time.
The image above ist from Marcelo Marques.