rust


Rust Borrow checker only complains about borrowing as mutable multiple times when a function that returns a reference with the same lifetime assigned


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() {
1. let mut some_val = NoLifetime {};
2. borrow_mut_function(&mut some_val);
3. borrow_mut_function(&mut some_val);
4. let num = 5;
5. let mut life_val = WithLifetime { field: &num };
6. borrow_lifetime(&mut life_val);
7. borrow_lifetime(&mut life_val);
8. let num_again = borrow_lifetime(&mut life_val);
9. borrow_lifetime(&mut life_val);
}
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 an oft requested feature, and is pending an internal refactoring of the compiler (MIR introduction) which is nearing completion.
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.

Related Links

How to concatenate a literal string with a const string?
Replacing a borrowed variable
Type hinting on Rust function calls
Nesting an iterator's loops
Do literal integral values have a specific type in Rust?
What value does the variable in the following code snippet have?
How do you import macros in submodules in Rust?
Local let like in caml
E0309: Constraining a generic type parameter's lifetime
Are nested matches a bad practice in idiomatic Rust?
Extern crate inside main method; module::Type vs main::module::Type
Iterating over named regex groups in Rust
How do I give a mutable reference to a builder but only an immutable reference to the built object?
Function returning a closure not working inside my filter
How do I get a pointer to a memory location when I know the memory address (without using std::ptr)? [duplicate]
How to expose a Rust `Vec<T>` to FFI?

Categories

HOME
user-interface
apache-nifi
plesk
matrix
functional-programming
adobe
apk
magento-2.0
bing-search
azure-data-lake
facebook-android-sdk
angular-meteor
roku
bellman-ford
yahoo
hystrix
graphdb
circleci
tiff
jodatime
asp.net-core-1.0
checkout
spring-shell
anova
prompt
list-comprehension
tortoisehg
adapter
tilemill
rust-cargo
actframework
caesar-cipher
kendo-listview
java-stream
formsauthenticationticket
love2d
jade4j
task-parallel-library
hypothesis-test
common-table-expression
deployd
hibernate-ogm
popen
mechanicalturk
portal
thread-exceptions
gcloud-node
jtag
simian
processor
i2b2
json-spirit
msgpack
google-maps-api-2
directx-9
canopy
knife
postal-code
textscan
physicsjs
clarion
kotlin-android-extensions
nssortdescriptor
android-audiomanager
signed
maven-shade-plugin
nanomsg
jcheckbox
google-hangouts
lync-2010
django-sites
strcmp
facebook-game-groups
ggts
infinite
codahale-metrics
chartfx
aero
oembed
2d-vector
multiscaleimage
onconfigurationchanged
nsmutablestring
stretch
popup-blocker
image-formats
autosize
prototypal-inheritance
dropshadow
nsrangeexception
azure-acs
aptitude
multiple-users
uninstaller
.net-services
firephp
processors
fxruby
rendering-engine

Resources

Database Users
RDBMS discuss
Database Dev&Adm
javascript
java
csharp
php
android
javascript
java
csharp
php
python
android
jquery
ruby
ios
html
Mobile App
Mobile App
Mobile App