I am testing my understanding of lifetimes in Rust by explicitly annotating function signatures and I created an example that I am not sure I understand.
In this example, I am simulating the concept of sharing a book and turning a page within it. To do this I am using a single mutable reference which I pass to a borrow_and_read function that updates the curr_page field of a Book struct. My Book struct and main function look like:
#[derive(Debug)]
pub struct Book<'a> {
pub title: &'a str,
pub curr_page: Option<i32>,
pub page_count: i32,
}
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
fn main() {
let mut the_book: Book = Book {
title: "The Book",
curr_page: None,
page_count: 104,
};
let a_book: &mut Book = &mut the_book;
borrow_and_read(a_book);
borrow_and_read(a_book);
observe_book(&*a_book);
}
pub fn observe_book<'a>(a_book: &'a Book<'a>) {
println!("Observing: {:?}", a_book);
}
(Playground)
For my first implementation of the borrow_and_read function, I let the compiler add annotations and everything compiled:
fn borrow_and_read(a_book: &mut Book) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
I then tried adding a single lifetime annotation specifying a lifetime for both the reference and the instance of the Book itself:
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
This yielded the following errors:
error[E0499]: cannot borrow `*a_book` as mutable more than once at a time
--> src/main.rs:25:21
|
24 | borrow_and_read(a_book);
| ------ first mutable borrow occurs here
25 | borrow_and_read(a_book);
| ^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
error[E0502]: cannot borrow `*a_book` as immutable because it is also borrowed as mutable
--> src/main.rs:27:18
|
24 | borrow_and_read(a_book);
| ------ mutable borrow occurs here
...
27 | observe_book(&*a_book);
| ^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
After thinking through what I had initially tried, I decided it made sense to separate the lifetimes of the mutable reference to a Book and the instance of Book itself. I then came up with this:
fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>)
where 'b : 'a {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
which does compile and output the expected results.
I am confused as to why my initial error message was that a_book was borrowed mutably more than once. I thought I would be ok passing around a single mutable reference since each usage of the reference understood that the reference was mutable. This thinking seems to be confirmed by the final implementation of my borrow_and_read function but I am not completely sure why specifying that the lifetime of the Book instance outlives the mutable reference with where 'b : 'a fixes my issue.
I am hoping to get a solid understanding of how using the same lifetime for both the mutable reference and Book instance yield the errors I got.
The problem with your original is that the lifetimes are too constrained. By making the borrow on the Book have the same length as the borrow on the book title ("The Book"), the mutable borrow is forced to last as long as the actual book itself, meaning that it can never be immutably borrowed.
Let's explore that. It'll be easier to examine your fixed version and then look at what the original does to constrain it.
fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>)
where 'b : 'a {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
This function has two lifetime parameters: one for the book itself and one for the mutable borrow on the book. We also constrain 'b: 'a, meaning that any borrows with lifetime 'a are valid for no longer than borrows with lifetime 'b. This is actually redundant, since the compiler can see that anyway. By having an argument with type &'a mut Book<'b>, 'a already can't last longer than 'b.
Now let's look at main. We'll call the lifetime on the book itself 'book. We'll call the lifetime on the mutable borrow of the book 'mtb. Finally, we'll call the immutable borrow (at observe_book) 'imb. Let's see how long each lifetime has to last.
// Initialize `the_book`. 'book has to start before this.
// Mutably borrow `the_book`. 'mtb has to start here.
let a_book: &mut Book = &mut the_book;
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Deref the mutable borrow and reborrow immutably.
// 'imb has to start here, so 'mtb has to end here.
// 'imb is a reference to `the_book`, so 'book has to still be active.
observe_book(&*a_book);
// The variables are no longer needed, so any outstanding lifetimes can end here
// That means 'imb and 'book end here.
So the crux of the issue here is that with this setup, 'mtb has to end before 'book. Now let's look at the original version of the function.
fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
match a_book.curr_page {
Some(page) => a_book.curr_page = Some(page + 1),
None => a_book.curr_page = Some(0),
};
}
Now we only have one lifetime parameter, which forces the lifetime of the title and the lifetime of the mutable borrow to be the same. That means that 'mtb and 'book have to be the same. But we just showed that 'mtb has to end before 'book! So with that contradiction, the compiler gives us an error. I don't know the technical details of why the error is cannot borrow*a_bookas mutable more than once at a time, but I imagine that the compiler thinks of "usages" of a variable similarly to how we talk about lifetimes. Since 'book has to last until the call to observe_book and 'mtb is the same as 'book, it treats the usage of 'book as a usage of the mutable borrow. Again, I'm not completely sure about that. It might be worth filing an issue to see if the message can be improved.
I did actually lie a little bit above. While Rust doesn't do implicit type coercion, it does do lifetime coercion. Borrows with longer lifetimes can be coerced to borrows with shorter lifetimes. That ultimately doesn't matter too much here, but it's worth knowing about.
The title of the book, a string literal, has the type &'static str, where 'static is a special lifetime that lasts for the whole duration of the program. The data is embedded into the binary of the program itself. When we initialize the_book, it could have the type Book<'static>, but it could equally be coerced to Book<'book> for some shorter lifetime 'book. When we take the mutable borrow we're forced to have 'book: 'mtb, but we still have no other constraints.
When we call the one-parameter version of borrow_and_read, 'book and 'mtb have to both be coerced down to a shorter, common lifetime. (in this case, since 'book: 'mtb, 'mtb would work - and indeed, it's the longest lifetime that would work). With the two-parameter version, no coercion is necessary. 'book and 'mtb can be used as is.
Now when we deref a_book and reborrow it immutably, no mutable borrows can be active. That means that mtb and the shorter lifetime that both 'book and 'mtb were coerced to have to end. But a_book has lifetime 'book and we're using it, so 'book can't end. Hence the error.
With the two-parameter version, 'book wasn't coerced to a shorter lifetime, so it could continue.
Related
I've encountered a confusing error about the use of a mutable and immutable borrow at the same time, after I expect the mutable borrow to end. I've done a lot of research on similar questions (1, 2, 3, 4, 5) which has led me to believe my problem has something to do with lexical lifetimes (though turning on the NLL feature and compiling on nightly doesn't change the result), I just have no idea what; my situation doesn't seem to fit into any of the scenarios of the other questions.
pub enum Chain<'a> {
Root {
value: String,
},
Child {
parent: &'a mut Chain<'a>,
},
}
impl Chain<'_> {
pub fn get(&self) -> &String {
match self {
Chain::Root { ref value } => value,
Chain::Child { ref parent } => parent.get(),
}
}
pub fn get_mut(&mut self) -> &mut String {
match self {
Chain::Root { ref mut value } => value,
Chain::Child { ref mut parent } => parent.get_mut(),
}
}
}
#[test]
fn test() {
let mut root = Chain::Root { value: "foo".to_string() };
{
let mut child = Chain::Child { parent: &mut root };
*child.get_mut() = "bar".to_string();
} // I expect child's borrow to go out of scope here
assert_eq!("bar".to_string(), *root.get());
}
playground
The error is:
error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
--> example.rs:36:36
|
31 | let mut child = Chain::Child { parent: &mut root };
| --------- mutable borrow occurs here
...
36 | assert_eq!("bar".to_string(), *root.get());
| ^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
I understand why an immutable borrow happens there, but I do not understand how a mutable borrow is used there. How can both be used at the same place? I'm hoping someone can explain what is happening and how I can avoid it.
In short, &'a mut Chain<'a> is extremely limiting and pervasive.
For an immutable reference &T<'a>, the compiler is allowed to shorten the lifetime of 'a when necessary to match other lifetimes or as part of NLL (this is not always the case, it depends on what T is). However, it cannot do so for mutable references &mut T<'a>, otherwise you could assign it a value with a shorter lifetime.
So when the compiler tries to reconcile the lifetimes when the reference and the parameter are linked &'a mut T<'a>, the lifetime of the reference is conceptually expanded to match the lifetime of the parameter. Which essentially means you've created a mutable borrow that will never be released.
Applying that knowledge to your question: creating a reference-based hierarchy is really only possible if the nested values are covariant over their lifetimes. Which excludes:
mutable references
trait objects
structs with interior mutability
Refer to these variations on the playground to see how these don't quite work as expected.
See also:
Why does linking lifetimes matter only with mutable references?
How do I implement the Chain of Responsibility pattern using a chain of trait objects?
What are the differences when getting an immutable reference from a mutable reference with self-linked lifetimes?
For fun, I'll include a case where the Rust standard library does this sort of thing on purpose. The signature of std::thread::scope looks like:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
The Scope that is provided to the user-defined function intentionally has its lifetimes tied in a knot to ensure it is only used in intended ways. This is not always the case since structs may be covariant or contravariant over their generic types, but Scope is defined to be invariant. Also, the only function that can be called on it is .spawn() which intentionally takes &'scope self as the self-parameter as well, ensuring that the reference does not have a shorter lifetime than what is given by scope.
Internally, the standard library contains this documentation (source):
Invariance over 'scope, to make sure 'scope cannot shrink, which is necessary for soundness.
Without invariance, this would compile fine but be unsound:
std::thread::scope(|s| {
s.spawn(|| {
let a = String::from("abcd");
s.spawn(|| println!("{a:?}")); // might run after `a` is dropped
});
});
Even if the lifetime of the reference is invariant with respect to itself, this still avoids many problems above because it uses an immutable reference and interior-mutability. If the parameter to .spawn() required &'scope mut self, then this would not work and run into the same problems above when trying to spawn more than one thread.
The issue isn't lexical lifetimes, and adding an explicit drop won't change the error. The issue is with the &'a mut Chain<'a>- that forces root to be borrowed for its entire life, rendering it useless after the borrow is dropped. As per the comment below, doing this with lifetimes is basically impossible. I would suggest using a box instead. Changing the struct to
pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
and adjusting the other methods as necesary. Alternatively, using an Rc<RefCell<Chain>> if you want the original to remain usable without consuming self.
I've encountered a confusing error about the use of a mutable and immutable borrow at the same time, after I expect the mutable borrow to end. I've done a lot of research on similar questions (1, 2, 3, 4, 5) which has led me to believe my problem has something to do with lexical lifetimes (though turning on the NLL feature and compiling on nightly doesn't change the result), I just have no idea what; my situation doesn't seem to fit into any of the scenarios of the other questions.
pub enum Chain<'a> {
Root {
value: String,
},
Child {
parent: &'a mut Chain<'a>,
},
}
impl Chain<'_> {
pub fn get(&self) -> &String {
match self {
Chain::Root { ref value } => value,
Chain::Child { ref parent } => parent.get(),
}
}
pub fn get_mut(&mut self) -> &mut String {
match self {
Chain::Root { ref mut value } => value,
Chain::Child { ref mut parent } => parent.get_mut(),
}
}
}
#[test]
fn test() {
let mut root = Chain::Root { value: "foo".to_string() };
{
let mut child = Chain::Child { parent: &mut root };
*child.get_mut() = "bar".to_string();
} // I expect child's borrow to go out of scope here
assert_eq!("bar".to_string(), *root.get());
}
playground
The error is:
error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
--> example.rs:36:36
|
31 | let mut child = Chain::Child { parent: &mut root };
| --------- mutable borrow occurs here
...
36 | assert_eq!("bar".to_string(), *root.get());
| ^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
I understand why an immutable borrow happens there, but I do not understand how a mutable borrow is used there. How can both be used at the same place? I'm hoping someone can explain what is happening and how I can avoid it.
In short, &'a mut Chain<'a> is extremely limiting and pervasive.
For an immutable reference &T<'a>, the compiler is allowed to shorten the lifetime of 'a when necessary to match other lifetimes or as part of NLL (this is not always the case, it depends on what T is). However, it cannot do so for mutable references &mut T<'a>, otherwise you could assign it a value with a shorter lifetime.
So when the compiler tries to reconcile the lifetimes when the reference and the parameter are linked &'a mut T<'a>, the lifetime of the reference is conceptually expanded to match the lifetime of the parameter. Which essentially means you've created a mutable borrow that will never be released.
Applying that knowledge to your question: creating a reference-based hierarchy is really only possible if the nested values are covariant over their lifetimes. Which excludes:
mutable references
trait objects
structs with interior mutability
Refer to these variations on the playground to see how these don't quite work as expected.
See also:
Why does linking lifetimes matter only with mutable references?
How do I implement the Chain of Responsibility pattern using a chain of trait objects?
What are the differences when getting an immutable reference from a mutable reference with self-linked lifetimes?
For fun, I'll include a case where the Rust standard library does this sort of thing on purpose. The signature of std::thread::scope looks like:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
The Scope that is provided to the user-defined function intentionally has its lifetimes tied in a knot to ensure it is only used in intended ways. This is not always the case since structs may be covariant or contravariant over their generic types, but Scope is defined to be invariant. Also, the only function that can be called on it is .spawn() which intentionally takes &'scope self as the self-parameter as well, ensuring that the reference does not have a shorter lifetime than what is given by scope.
Internally, the standard library contains this documentation (source):
Invariance over 'scope, to make sure 'scope cannot shrink, which is necessary for soundness.
Without invariance, this would compile fine but be unsound:
std::thread::scope(|s| {
s.spawn(|| {
let a = String::from("abcd");
s.spawn(|| println!("{a:?}")); // might run after `a` is dropped
});
});
Even if the lifetime of the reference is invariant with respect to itself, this still avoids many problems above because it uses an immutable reference and interior-mutability. If the parameter to .spawn() required &'scope mut self, then this would not work and run into the same problems above when trying to spawn more than one thread.
The issue isn't lexical lifetimes, and adding an explicit drop won't change the error. The issue is with the &'a mut Chain<'a>- that forces root to be borrowed for its entire life, rendering it useless after the borrow is dropped. As per the comment below, doing this with lifetimes is basically impossible. I would suggest using a box instead. Changing the struct to
pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
and adjusting the other methods as necesary. Alternatively, using an Rc<RefCell<Chain>> if you want the original to remain usable without consuming self.
I am calling closures with a fold function inside another closure. While I am intending for nothing in this some_closure function to live outside the closure environment, I am receiving an error that I am dropping a value while it is still borrowed.
I have tried removing all lifetime specifiers from some_closure, because I find the compiler is much smarter than myself at figuring out lifetimes, but I'm also finding no success in this (the compiler will always ask for lifetime specifiers leading up to the point of the shown example).
What I would desire to do here is to specify a lifetime restricted to the length of the closure inside the function, rather than the function itself. But I have a feeling that what I think is the problem may not actually be my problem, and that there is some gap in my understanding of lifetimes in closures.
I've tried to minimize the example as much as possible:
struct HoldStr<'a>(&'a str);
fn clone_slice_borrows_into_vec<'a>() -> impl Fn(&[&'a HoldStr]) -> Vec<&'a HoldStr<'a>> {
|slice| {
let mut temp = vec![];
temp.clone_from_slice(slice);
temp
}
}
fn clone_slice_borrows_into_vec_same<'a>() -> impl Fn(&[&'a HoldStr]) -> Vec<&'a HoldStr<'a>> {
// Same as last function for the sake of example, but one can assume it does something else
}
fn some_closure<'a>() -> impl Fn() {
|| {
let my_vec = vec![HoldStr("one"), HoldStr("two")];
let my_vec_holding_borrow: Vec<&'a HoldStr> = my_vec.iter().collect();
let called_closures: [Box<dyn Fn(&[&'a HoldStr]) -> Vec<&'a HoldStr<'a>>>; 2] = [
Box::new(clone_slice_borrows_into_vec()),
Box::new(clone_slice_borrows_into_vec_same())
];
let _result = called_closures
.iter()
.fold(my_vec_holding_borrow, |acc, closure| closure(&acc));
}
}
I would expect everything to be dropped by the end of the closure inside some_closure and for this to be fine, especially since I am specifying that lifetime 'a does not relate to anything the function itself returns. But it seems that the borrowed value is expected to live until the end of the function itself. I get this error:
error[E0597]: `my_vec` does not live long enough
--> src/lib.rs:61:51
|
## | fn some_closure<'a>() -> impl Fn() {
| -- lifetime `'a` defined here
...
## | let my_vec_holding_borrow: Vec<&'a HoldStr> = my_vec.iter().collect();
| ---------------- ^^^^^^ borrowed value does not live long enough
| |
| type annotation requires that `my_vec` is borrowed for `'a`
...
## | }
| - `my_vec` dropped here while still borrowed
I'd be happy to hear anything from how to resolve the error, to that I've been going about this the wrong way in the first place.
You need higher-rank trait bounds for your closure types:
fn clone_slice_borrows_into_vec() -> impl for<'a> Fn(&[&'a HoldStr]) -> Vec<&'a HoldStr<'a>> {
...
(Full code in the playground)
The lifetime 'a isn't fixed for your closure. It should return a vector of references with lifetime 'a for any input slice with references of this lifetime. Your code used an externally fixed lifetime instead, which could be chosen by the caller of clone_slice_borrows_into_vec().
If you have a funciton definition like
fn foo<'a>() -> &'a Foo
then it's basically always a mistake. This lets the caller request an arbitrary lifetime, and the function promises to create a reference of this lifetime out of thin air, which is only possible if it gets static references from some global storage, in which case it should simply return &'static Foo.
This question already has an answer here:
Cannot borrow immutable borrowed content as mutable
(1 answer)
Closed 5 years ago.
I am recreating the arithmetic operations in binary by representing numbers with a vector of booleans. Since the size of each vector can vary, I made a function to match the length of each vector:
fn match_lengths(mut bit_vec0: Vec<bool>, mut bit_vec1: Vec<bool>) -> (Vec<bool>, Vec<bool>) {
{
let (mut shorter, longer) = if bit_vec0.len() < bit_vec1.len() {
(&bit_vec0, &bit_vec1)
} else {
(&bit_vec1, &bit_vec0)
};
let bit_sign = match shorter.last() {
Some(content) => *content,
None => false,
};
for _ in shorter.len()..longer.len() {
shorter.push(bit_sign);
}
}
(bit_vec0, bit_vec1)
}
I get the error
error[E0596]: cannot borrow immutable borrowed content `*shorter` as mutable
--> src/main.rs:15:13
|
15 | shorter.push(bit_sign); // Error here
| ^^^^^^^ cannot borrow as mutable
Even though I declared it with the mut specifier.
The type of shorter is a reference, more precisely a &Vec<bool>, which means it refers to a Vec<bool> which it is not allowed to mutate1.
Declaring the variable as mut shorter only makes the shorter variable mutable, allowing you to e.g. use shorter = ... assignment to make it refer to a different Vec<bool>. Regardless of the variable's mutability, a shared reference of type &Vec<bool> is not allowed to mutate the object it refers to.
What you need to do is make a mutable reference of type &mut Vec<bool>, using &mut bit_vec0 and &mut bit_vec1. This change makes the code compile, and at that point shorter no longer needs to be mut.
Finally, and this is unrelated to the question, match_lengths accepts bit_vec0 and bit_vec1 by value, modifies them, and returns them. Although that certainly works, it is more idiomatic to accept mutable references. Such an approach is more ergonomic for the caller, and more clearly signals the fact that the function doesn't really "return" anything, it really modifies existing objects.
With those modifications, the function looks like this:
fn match_lengths(bit_vec0: &mut Vec<bool>, bit_vec1: &mut Vec<bool>) {
let (shorter, longer) = if bit_vec0.len() < bit_vec1.len() {
(bit_vec0, bit_vec1)
} else {
(bit_vec1, bit_vec0)
};
let bit_sign = match shorter.last() {
Some(content) => *content,
None => false,
};
for _ in shorter.len()..longer.len() {
shorter.push(bit_sign);
}
}
1
This sounds much like C++'s const, but the guarantee in Rust is even stronger than that of const: not only is a shared (non-mut) reference not allowed to mutate the object it refers to, but neither is anyone else! Rust's compiler and runtime prevent creation of a mut reference to an object while any shared references to that object exist.
I'm having problem with some Rust code where I'm being allowed to borrow something as mutable more than once on certain conditions (first confusing part), but not others.
I've written the following example to illustrate:
(Playground)
struct NoLifetime {}
struct WithLifetime <'a> {
pub field: &'a i32
}
fn main() {
let mut some_val = NoLifetime {};
borrow_mut_function(&mut some_val);
borrow_mut_function(&mut some_val); // Borrowing as mutable for the second time.
let num = 5;
let mut life_val = WithLifetime { field: &num };
borrow_lifetime(&mut life_val);
borrow_lifetime(&mut life_val); // Borrowing as mutable for the second time.
let num_again = borrow_lifetime(&mut life_val); // Borrow, assign lifetime result
borrow_lifetime(&mut life_val); // Compiler: cannot borrow `life_val` as mutable more than once
}
fn borrow_mut_function(val_in: &mut NoLifetime) -> String {
"abc".to_string()
}
fn borrow_lifetime<'a>(val_in: &'a mut WithLifetime) -> &'a i32 {
val_in.field
}
If you see, I can borrow both some_val, and life_val as mutable more than once. However, after assigning the return value of borrow_lifetime, I can no longer borrow.
My questions are the following:
From 'The Rules' about Borrowing in the Rust Book, I'm supposed to have 'exactly one mutable reference' in scope to the same value. However, in the code above I'm borrowing as mutable every time I call a borrow_ function.
Why is the same type of borrowing not allowed when I have a function that returns something with the same lifetime as the parameter, and I assign that parameter.
Any help would be appreciated. I imagine what is happening here is that I am misunderstanding what 'borrowing as mutable' really means, and when to determine that something is being borrowed as mutable.
Chris already gave the gist of it, but I think it is worth explaining further.
There are 2 ways to transfer ownership in Rust:
moving is a permanent transfer
borrowing is a temporary transfer, ownership is expected to be returned
Rust, like many other languages, models time passing using a stack of lexical scopes. As a result, for now, a borrow starts where it is created and extend until the end of its scope.
Thus, the questions of when a borrow ends is akin to asking what scope is the borrow created in.
Let's review your example with numbered lines:
fn main() {
let mut some_val = NoLifetime {}; // 1
borrow_mut_function(&mut some_val); // 2
borrow_mut_function(&mut some_val); // 3
//
let num = 5; // 4
let mut life_val = WithLifetime { field: &num }; // 5
borrow_lifetime(&mut life_val); // 6
borrow_lifetime(&mut life_val); // 7
//
let num_again = borrow_lifetime(&mut life_val); // 8
borrow_lifetime(&mut life_val); // 9
}
When a function is called, the argument is borrowed:
at least for the duration of the function call
up to the moment the result is dropped, if the result shares a lifetime with the argument
So, let's look at this:
on line (2) and (3) you call borrow_mut_function which returns a String: the result does not share any lifetime with the argument, so the argument is only borrowed for the lifetime of the function call.
on line (6) and (7) you call borrow_lifetime which returns a &'a i32: the result shares a lifetime with the argument, so the argument is borrowed until the end of the scope of the result... which is immediately since the result is not used.
on line (8) you call borrow_lifetime which returns a &'a i32 and you assign the result to num_again: the result shares a lifetime with the argument, so the argument is borrowed until the end of the scope of num_again.
on line (9) you call borrow_lifetime however its argument is still borrow by num_again so the call is illegal.
That's it, this is how Rust works today.
In the future, there is a call for non-lexical borrows. That is, the compiler would realize that:
num_again is never used
num_again does not have a specific destructor (no Drop implementation)
and could therefore decide that its borrow ends sooner than the end of the lexical scope.
This is about the scope of the borrow, and whether you keep the borrow alive. In most of the above calls, some_val is borrowed during the function call, but returned afterwards when the function returns.
In the exception case:
let num_again = borrow_lifetime(&mut life_val); //Borrow, assign lifetime result
You're borrowing life_val during the call to borrow_lifetime, but since the return value has the same lifetime as the parameter ('a), the borrow's scope is extended to include the lifetime of num_again, ie until the end of the function. It would be unsafe to borrow life_val again, since num_again is still a reference into it.