Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
50 changes: 50 additions & 0 deletions exercises/16_lifetimes/lifetimes4.rs
Original file line number Diff line number Diff line change
@@ -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}");
}
}
15 changes: 15 additions & 0 deletions rustlings-macros/info.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
60 changes: 60 additions & 0 deletions solutions/16_lifetimes/lifetimes4.rs
Original file line number Diff line number Diff line change
@@ -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}");
}
}