Jump to content

Rust Error : “lifetime may not live long enough” — why &static fn(T) - T doesn’t mean what you think

From JOHNWICK
Revision as of 08:01, 16 November 2025 by PC (talk | contribs) (Created page with "You’ve got a tidy little generic: pub struct Test<T> { f: &'static fn(T) -> T, } …and Rust fires back: the parameter type T may not live long enough
help: consider adding a bound T: 'static Why is the lifetime of a reference to a function pointer tangled up with the lifetime of T? After all, code lives forever, right? Let’s unpack what the compiler is protecting you from, and then fix it in a few idiomatic ways. The core intuition * &'static fn(T)...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

You’ve got a tidy little generic: pub struct Test<T> {

   f: &'static fn(T) -> T,

} …and Rust fires back: the parameter type T may not live long enough
help: consider adding a bound T: 'static Why is the lifetime of a reference to a function pointer tangled up with the lifetime of T? After all, code lives forever, right? Let’s unpack what the compiler is protecting you from, and then fix it in a few idiomatic ways.


The core intuition

  • &'static fn(T) -> T says: I hold a reference that’s valid for the entire program to a function which takes a T and returns a T.
  • If T is not 'static, it might hide a short-lived borrow inside it, e.g. T = &'a i32.
  • A 'static reference to that function can be called after 'a has ended. If the function returns that &'a i32, you get a reference that points to a dead stack frame. 💥

Rust doesn’t let you promise the function pointer outlives everything while letting the T it manipulates be allowed to carry “short” lifetimes. Hence the nudge: “add T: 'static”. In short: a 'static handle to a function that produces a T forces T to be 'static, otherwise you could manufacture dangling references.


Two clean fixes (pick one) 1) Don’t borrow the function pointer at all Plain function pointers are already 'static. There’s rarely a reason to take &fn(...) instead of fn(...): pub struct Test<T> {

   f: fn(T) -> T, // no reference, no lifetime trouble

} This compiles without T: 'static, because the function pointer value is 'static but doesn’t force T to be 'static. You can still call it with T = &'a i32 and get a &'a i32 back, and the borrow checker will ensure you only use that within 'a. When to choose this: You only need real, free-standing functions (not closures), and you don’t need to borrow a pointer to the pointer.


2) Make the function reference non-'static If you truly want to borrow a function pointer (or a callable) from somewhere, tie it to a caller-provided lifetime: pub struct Test<'a, T> {

   f: &'a fn(T) -> T,            // or:
   // f: &'a dyn Fn(T) -> T,     // if you want closures too

} Now the function reference is only guaranteed to be valid for 'a, so using it can’t outlive the lifetimes that may be hidden inside T. When to choose this: You’re passing in a callable that you don’t own (e.g., a borrowed closure or a borrowed function pointer) and you want the struct to be valid only as long as that callable is.


“But why isn’t &'static fn(T) -> T okay for some T?” Imagine (hypothetically) you could do this: fn make_static_fn<T>(_: T) -> &'static fn(T) -> T { todo!() } let f: &'static fn(_) -> _; {

   let a = 42;
   // Pretend the compiler allowed it:
   f = make_static_fn(&a); // here T is `&'a i32`

} // Much later … let r = (f)(&0); // From the type system’s POV, `r` has lifetime `'a` (!) If this compiled, r would be a reference that must still borrow a from that inner block—even though a is long gone. That’s the kind of time travel Rust refuses to enable. The 'static on the reference doesn’t just say “the code lives forever”—it anchors the ability to produce T forever, and that only makes sense if T itself can live forever (T: 'static).


Bonus: closures vs function pointers

  • fn(T) -> T is a function pointer type: only free functions (or non-capturing closures, which can coerce) fit here.
  • dyn Fn(T) -> T is a closure trait object: it can capture environment. If you want to hold closures, use Box<dyn Fn(T) -> T> or &'a dyn Fn(T) -> T.

Example with a borrowed, capturing closure: pub struct Test<'a, T> {

   f: &'a dyn Fn(T) -> T,

} fn demo() {

   let x = 10;
   let add_x = |n: i32| n + x;   // captures `x`
   let t = Test { f: &add_x };    // `t` can’t outlive `x`
   assert_eq!((t.f)(5), 15);

} Advanced pattern: higher-ranked lifetimes (HRTB) Sometimes you want a function that works for any lifetime it’s given, e.g. a function that takes a reference and returns that same reference. You can write that with an explicit for<'a>: type IdRef = for<'a> fn(&'a i32) -> &'a i32; pub struct Test {

   f: IdRef, // callable for any `'a`

} This avoids baking a specific 'a into the type. You generally won’t combine this with a generic T because the lifetime is inside T; instead, name the reference type directly as above.


Practical checklist

  • ✅ Prefer fn(T) -> T over &fn(T) -> T. The pointer itself is 'static; avoid borrowing it.
  • ✅ If you must borrow, add a lifetime: Test<'a, T> { f: &'a fn(T) -> T } (or &'a dyn Fn).
  • ✅ Only require T: 'static when you truly need to store the callable (or its outputs) beyond any borrow.
  • ✅ Use dyn Fn if you need closures; use fn for plain functions.
  • ✅ Reach for HRTB (for<'a>) when you need “same-lifetime-in, same-lifetime-out” across any 'a.


Putting it all together Here are two idiomatic, compiling versions you can drop in today: Function pointer (no borrow, most ergonomic): pub struct Test<T> {

   f: fn(T) -> T,

} impl<T> Test<T> {

   pub fn new(f: fn(T) -> T) -> Self { Self { f } }
   pub fn call(&self, t: T) -> T { (self.f)(t) }

} Borrowed callable with closures support: pub struct Test<'a, T> {

   f: &'a dyn Fn(T) -> T,

} impl<'a, T> Test<'a, T> {

   pub fn new(f: &'a dyn Fn(T) -> T) -> Self { Self { f } }
   pub fn call(&self, t: T) -> T { (self.f)(t) }

} Both avoid the unsound promise that a 'static-lived callable can create non-'static values forever.


Summary You got the error because &'static fn(T) -> T would let you produce T forever—even when T secretly contains short-lived borrows. Rust blocks that unless you assert T: 'static. The easy, idiomatic fixes are:

  • Don’t borrow the function pointer: use fn(T) -> T.
  • Or borrow it with a non-'static lifetime: &'a fn(T) -> T / &'a dyn Fn(T) -> T.

Once you make that change, the compiler — and your future self — will breathe easier.