Skip to content

← Ownership III: Lifetimes, Explicitly step 17 of 22

Hard End-to-End

Store owned and borrowed closures in registries

Complete two registries that store callable handlers. One accepts handlers that do not borrow short-lived data. The other can store handlers borrowing data that remains valid for a chosen lifetime.

pub type Handler = Box<dyn Fn(i64) -> i64>;
pub type BorrowedHandler<'a> = Box<dyn Fn(i64) -> i64 + 'a>;

Keep the supplied aliases, fields, Default derives and signature check. Complete both call_all methods, fix the bound on BorrowingRegistry::register, and implement:

pub fn run(offsets: Vec<i64>, x: i64) -> (Vec<i64>, Vec<i64>)

Each register appends one handler. call_all(&self, x) calls every handler once, in registration order, and collects the results without removing the handlers. Every handler receives the same x, not the previous result.

In run, register one handler per offset in each registry:

  • For Registry, capture a copy of the i64 offset by value.
  • For BorrowingRegistry, capture a reference to that element of offsets. Keep offsets alive while the borrowing registry is in use.

Each handler returns x.wrapping_add(offset). Return the owning registry's results first and the borrowing registry's results second. Both vectors preserve offset order and contain the same numbers. For [1, 2, 3] and x = 10, return ([11, 12, 13], [11, 12, 13]). Empty offsets produce two empty vectors.

Inputs span the full i64 range. Use wrapping_add explicitly: i64::MAX + 1 wraps to i64::MIN, and i64::MIN + (-1) wraps to i64::MAX. This defines the result independently of overflow-check settings.

A type bound is not a promise to live forever

&'static T is a reference whose referent remains valid for the static lifetime. T: 'static instead says that the type satisfies that lifetime bound. It rules out stored borrows with shorter lifetimes; it does not force a value of that type to remain alive forever.

For example, String, Vec<u8> and i64 satisfy 'static, even when their values are created and dropped inside one function. An owned container can still hold borrowed data: Vec<&'a str> is not automatically 'static for an arbitrary 'a. Ownership alone does not determine the bound.

Both registries own their boxed closure values. The difference is what those closures are permitted to borrow. A closure capturing a copied integer can satisfy 'static; one capturing a reference into a local vector generally cannot.

Choose the stored trait object's lifetime

In the supplied Handler type alias, the omitted trait-object lifetime defaults to 'static. Registry::register therefore requires F: Fn(i64) -> i64 + 'static so it can store the closure in that field.

BorrowedHandler<'a> explicitly permits borrows valid for 'a. Match that requirement in BorrowingRegistry::register with F: Fn(i64) -> i64 + 'a. The starter unnecessarily asks for 'static, which rejects the borrowed closure in the supplied signature check with E0521.

That check must compile for an arbitrary 'a; requiring it to be 'static would defeat the borrowing API. Keep the check unchanged. It verifies that registration accepts a closure capturing the table reference, but does not run the closure or verify all of run's capture choices.

Moving a reference still leaves a borrow

In for &offset in &offsets, the pattern copies an integer. A move closure can then capture that integer independently of the vector.

In for slot in &offsets, slot is an &i64. A move closure captures the reference itself; it does not copy the referenced integer into owned storage merely because the closure says move. Use *slot inside that handler's calculation.

Construct both registries with their supplied Default implementations. In call_all, iterate over the stored handlers and call each with x. A borrowed boxed Fn trait object can be called directly.