From 10a28ca074029bb8482b073baa2f9a9c9baa37e6 Mon Sep 17 00:00:00 2001 From: "Simon B. Gasse" Date: Mon, 13 Apr 2026 22:02:22 +0200 Subject: [PATCH] Add exercise involving lifetime elision --- dev/Cargo.toml | 2 + exercises/16_lifetimes/lifetimes4.rs | 50 +++++++++++++++++++++++ rustlings-macros/info.toml | 15 +++++++ solutions/16_lifetimes/lifetimes4.rs | 60 ++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 exercises/16_lifetimes/lifetimes4.rs create mode 100644 solutions/16_lifetimes/lifetimes4.rs diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 4f725b70..91540ade 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -134,6 +134,8 @@ bin = [ { name = "lifetimes2_sol", path = "../solutions/16_lifetimes/lifetimes2.rs" }, { name = "lifetimes3", path = "../exercises/16_lifetimes/lifetimes3.rs" }, { name = "lifetimes3_sol", path = "../solutions/16_lifetimes/lifetimes3.rs" }, + { name = "lifetimes4", path = "../exercises/16_lifetimes/lifetimes4.rs" }, + { name = "lifetimes4_sol", path = "../solutions/16_lifetimes/lifetimes4.rs" }, { name = "tests1", path = "../exercises/17_tests/tests1.rs" }, { name = "tests1_sol", path = "../solutions/17_tests/tests1.rs" }, { name = "tests2", path = "../exercises/17_tests/tests2.rs" }, diff --git a/exercises/16_lifetimes/lifetimes4.rs b/exercises/16_lifetimes/lifetimes4.rs new file mode 100644 index 00000000..9b7583e4 --- /dev/null +++ b/exercises/16_lifetimes/lifetimes4.rs @@ -0,0 +1,50 @@ +// Sometimes, we have structs which hold on to data temporarily. A use-case of +// this could be a routing component which accepts a connection and returns it to +// another recipient. To avoid copying the data, we just accept a reference with +// lifetime and return this reference later. +// +// In the example below, we create a `Router` instance in a limited scope. It +// accepts a connection reference created in the enclosing scope and returns it. +// In theory, this should be possible given that the connection reference outlives +// the scope from which it is returned. However, the borrow checker does not +// seem to understand it. What can we do about that? + +// TODO: Fix the compiler error about `router` not living long enough without changing `main` + +struct Router<'a> { + connection: Option<&'a u64>, +} + +impl<'a> Router<'a> { + fn new() -> Self { + Self { connection: None } + } + + fn accept_connection(&mut self, connection: &'a u64) { + self.connection = Some(connection); + } + + fn return_connection(&mut self) -> Option<&u64> { + self.connection.take() + } +} + +// Do not change `main` +fn main() { + let connection = &123; + + let returned_connection = { + // Create router within scope + let mut router = Router::new(); + + // Accept connection which lives longer than the router + router.accept_connection(connection); + + // Return connection which **should** live longer than the router + router.return_connection() + }; + + if let Some(connection) = returned_connection { + println!("The connection is {connection}"); + } +} diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 353d405c..1bff88df 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -832,6 +832,21 @@ dir = "16_lifetimes" test = false hint = """Let the compiler guide you :)""" +[[exercises]] +name = "lifetimes4" +dir = "16_lifetimes" +test = false +hint = """ +Lifetimes can be elided if the compiler can infer a sensible default choice: +https://doc.rust-lang.org/reference/lifetime-elision.html + +In most cases, the default choice is correct. +But occasionally, the default choice does not follow our intent. +Can you spot an elided lifetime which might not be correct? +You can think about what the compiler's default choice is for every elided lifetime. +Where could you add an explicit lifetime annotation to better express what we want? +""" + # TESTS [[exercises]] diff --git a/solutions/16_lifetimes/lifetimes4.rs b/solutions/16_lifetimes/lifetimes4.rs new file mode 100644 index 00000000..1b40cedd --- /dev/null +++ b/solutions/16_lifetimes/lifetimes4.rs @@ -0,0 +1,60 @@ +// Sometimes, we have structs which hold on to data temporarily. A use-case of +// this could be a routing component which accepts a connection and returns it to +// another recipient. To avoid copying the data, we just accept a reference with +// lifetime and return this reference later. +// +// In the example below, we create a `Router` instance in a limited scope. It +// accepts a connection reference created in the enclosing scope and returns it. +// In theory, this should be possible given that the connection reference outlives +// the scope from which it is returned. However, the borrow checker does not +// seem to understand it. What can we do about that? + +struct Router<'a> { + connection: Option<&'a u64>, + // ^^ lifetime of the connection reference + // which may outlive the `Router` itself +} + +impl<'a> Router<'a> { + fn new() -> Self { + Self { connection: None } + } + + fn accept_connection(&mut self, connection: &'a u64) { + self.connection = Some(connection); + } + + fn return_connection(&mut self) -> Option<&'a u64> { + // added lifetime annotation ^^ + // + // Without annotation, the compiler infers the output reference + // to have the lifetime of the only input reference + // -> the lifetime of `&mut self`. + self.connection.take() + } +} + +fn main() { + let connection = &123; + + let returned_connection = { + // Create router within scope + let mut router = Router::new(); + + // Accept connection which lives longer than the router + router.accept_connection(connection); + + // Return connection which **should** live longer than the router + router.return_connection() + // ^^^^^^^^^^^^^^^^^^^^ + // + // Without the explicit lifetime annotation in `return_connection`, + // the reference from `return_connection` has the lifetime of `router`. + // We are returning the reference from the scope, requiring it to outlive it, + // so the compiler complains about `router` not living long enough. + }; + + if let Some(connection) = returned_connection { + println!("The connection is {connection}"); + } +}