Merge 10a28ca074029bb8482b073baa2f9a9c9baa37e6 into 06ca7b8718547a806b2a54dc8d53e4f008edce60

This commit is contained in:
Simon B. Gasse 2026-05-14 13:36:31 +08:00 committed by GitHub
commit 83606e98cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 127 additions and 0 deletions

View File

@ -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" },

View File

@ -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}");
}
}

View File

@ -838,6 +838,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]]

View File

@ -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}");
}
}