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 would one return a function from a function in Rust?
Lifetime on trait returning iterator
Mutable borrow of self doesn't change to immutable
How do I specify the linker path in Rust?
Are raw pointers to temporaries ok in Rust?
How to destructure tuple struct with reference
Unable to use std::process::Command to SSH - No such file or directory
“borrowed value does not live long enough” error in trie insertion
How to check for EOF in read_line in Rust 1.12?
Can I create an owned pointer to a stack object
How do I abort a Rust process?
Is it possible to disable Rust's lifetime elision?
How to implement Index over a wrapped HashMap?
Why is it allowed to pass the parameter value by reference with the duplicated sign &? [duplicate]
How to handle floating point exceptions (fpe) with Rust?
Mismatched types for returned struct (expected <K, V>, found <&K, &V>)

Categories

HOME
qt
visual-studio
keyboard
blast
yaml
vagrant
facebook-messenger
barcode-scanner
long-integer
typeerror
semantic-web
greasemonkey
hyperledger
azure-mobile-services
bittorrent
impala
ui-automation
save
pyresttest
phpseclib
siesta
parsley.js
vimeo
android-externalstorage
spring-amqp
multi-step
jquery-file-upload
powermock
pushdown-automaton
jodatime
jpa-2.0
twisted
reselect
hidden
string-formatting
sharedpreferences
mime-types
ini
pdf.js
spreadsheetgear
marquee
php-5.3
mdns
apptentive
jfxtras
ftp-server
uivisualeffectview
ms-dos
mv
aurelia-http-client
text-decorations
cheat-engine
python-sounddevice
python-jira
get-event-store
asihttprequest
autorelease
django-static-precompiler
image-registration
photoswipe
extjs4.1
algebra
glimpse
oid
ipywidgets
libharu
spring-mongo
static-methods
frame-grab
gulp-uglify
okta-api
helm
stack-smash
bjam
blockquote
360-degrees
fantom
darwin
relational-model
ildasm
window-resize
xhprof
nachos
stripe.net
zend-search-lucene
proxy-server
hints
pacman
bcdedit
arel
dojo-1.9
bll
anonymous-types
chunking
raw-data
getimagesize
eventaggregator
mysqli-multi-query
html-components
processors
server-load

Resources

Mobile Apps Dev
Database Users
javascript
java
csharp
php
android
MS Developer
developer works
python
ios
c
html
jquery
RDBMS discuss
Cloud Virtualization
Database Dev&Adm
javascript
java
csharp
php
python
android
jquery
ruby
ios
html
Mobile App
Mobile App
Mobile App