Every now and then, I run into the same issue with borrowing (or not borrowing) mutable variables inside a loop, and I finally sat down and compiled a minimal example. As a result, the code is a little silly, but it is the shortest version that I could come up with that highlights the problem:
struct Association {
used: bool,
key: usize,
value: String,
}
impl Association {
fn new(key: usize, value: &str) -> Self {
Association{used: false, key: key, value: value.to_string()}
}
}
fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
for k in 0.. {
for a in data {
if a.key == k && !a.used {
a.used = true;
return Some(&a.value);
}
}
}
None
}
fn main() {
let mut assoc = vec![
Association::new(7, "Hello"),
Association::new(9, "World")
];
println!("{}", find_unused(&mut assoc).unwrap());
println!("{}", find_unused(&mut assoc).unwrap());
}
This will fail with an error because data was moved before. If I borrow it instead, it will fail because it was borrowed before. I would like to understand exactly what is happening and how to solve it in general. In particular, I do not want to change the structure of the code, even if it is silly. I do not want to implement a workaround, because this is just a minimal example: Please assume that the nesting of the loops is the "right" way to do it, even if it is utterly silly here, which it definitely is.
I would only like to know how to communicate to the borrow checker that what is happening here is actually ok. I know of one way to do this:
fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
for k in 0.. {
for j in 0..data.len() {
if data[j].key == k && !data[j].used {
data[j].used = true;
return Some(&data[j].value);
}
}
}
None
}
This compiles without error and works as intended. In my naïve understanding, there should be a way to express the above with iterators instead of indexing, and I would like to know how that would be done.
The behavior you want is possible if you:
make fresh mutable borrows in each nested loop
And wait for rustc to integrate more Polonius capabilities. (Can experiment now with cargo +nightly rustc -- -Z polonius). While you wait, you could use unsafe like in this post: Borrow checker complains about multiple borrowing, when only one will ever occur
Keeping the rest of your code the same and focussing on this part, but with the return commented out:
fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
for k in 0.. {
for a in data { // Error - but rustc says what to do here
if a.key == k && !a.used {
a.used = true;
// return Some(&a.value);
}
}
}
None
}
The error messages from rustc say what to do and why. The mutable borrow in the parameters is moved (used up) by the call to into_iter() in for a in data.
rustc recommends a mutable re-borrow. This way we don't try to borrow something that has already been moved. Making that change (and keeping the return commented-out for now), the following now type-checks:
fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
for k in 0.. {
for a in &mut *data { // we took rustc's suggestion
if a.key == k && !a.used {
a.used = true;
// return Some(&a.value);
}
}
}
None
}
If we un-comment the return, we get an error saying that the lifetime of the value we're returning doesn't match 'a.
But if we run with cargo +nightly rustc -- -Z polonius, the code type-checks.
#stargateur suggested trying polonius in a comment
Here's my guess as to why Polonius helps:
For the current rustc borrow-checker, lifetimes in function signatures are treated in a relatively coarse and simplistic way within the body of the function. I think what's happening here is that 'a is supposed to span the entire function body, but the fresh mutable borrow for a only spans part of the function body, so Rust doesn't unify them.
Polonius tracks origins of borrows in types. So it knows that the lifetime of &a.value comes from a, which comes from data, which has lifetime 'a, so Polonius knows the return is OK.
I'm basing this on The Polonius Talk but The Book is probably more up-to-date
Related
I'm trying to write a function which pushes an element onto the end of a sorted vector only if the element is larger than the last element already in the vector, otherwise returns an error with a ref to the largest element. This doesn't seem to violate any borrowing rules as far as I cant tell, but the borrow checker doesn't like it. I don't understand why.
struct MyArray<K, V>(Vec<(K, V)>);
impl<K: Ord, V> MyArray<K, V> {
pub fn insert_largest(&mut self, k: K, v: V) -> Result<(), &K> {
{
match self.0.iter().next_back() {
None => (),
Some(&(ref lk, _)) => {
if lk > &k {
return Err(lk);
}
}
};
}
self.0.push((k, v));
Ok(())
}
}
error[E0502]: cannot borrow `self.0` as mutable because it is also borrowed as immutable
--> src/main.rs:15:9
|
6 | match self.0.iter().next_back() {
| ------ immutable borrow occurs here
...
15 | self.0.push((k, v));
| ^^^^^^ mutable borrow occurs here
16 | Ok(())
17 | }
| - immutable borrow ends here
Why doesn't this work?
In response to Paolo Falabella's answer.
We can translate any function with a return statement into one without a return statement as follows:
fn my_func() -> &MyType {
'inner: {
// Do some stuff
return &x;
}
// And some more stuff
}
Into
fn my_func() -> &MyType {
let res;
'outer: {
'inner: {
// Do some stuff
res = &x;
break 'outer;
}
// And some more stuff
}
res
}
From this, it becomes clear that the borrow outlives the scope of 'inner.
Is there any problem with instead using the following rewrite for the purpose of borrow-checking?
fn my_func() -> &MyType {
'outer: {
'inner: {
// Do some stuff
break 'outer;
}
// And some more stuff
}
panic!()
}
Considering that return statements preclude anything from happening afterwards which might otherwise violate the borrowing rules.
If we name lifetimes explicitly, the signature of insert_largest becomes fn insert_largest<'a>(&'a mut self, k: K, v: V) -> Result<(), &'a K>. So, when you create your return type &K, its lifetime will be the same as the &mut self.
And, in fact, you are taking and returning lk from inside self.
The compiler is seeing that the reference to lk escapes the scope of the match (as it is assigned to the return value of the function, so it must outlive the function itself) and it can't let the borrow end when the match is over.
I think you're saying that the compiler should be smarter and realize that the self.0.push can only ever be reached if lk was not returned. But it is not. And I'm not even sure how hard it would be to teach it that sort of analysis, as it's a bit more sophisticated than the way I understand the borrow checker reasons today.
Today, the compiler sees a reference and basically tries to answer one question ("how long does this live?"). When it sees that your return value is lk, it assigns lk the lifetime it expects for the return value from the fn's signature ('a with the explicit name we gave it above) and calls it a day.
So, in short:
should an early return end the mutable borrow on self? No. As said the borrow should extend outside of the function and follow its return value
is the borrow checker a bit too strict in the code that goes from the early return to the end of the function? Yes, I think so. The part after the early return and before the end of the function is only reachable if the function has NOT returned early, so I think you have a point that the borrow checked might be less strict with borrows in that specific area of code
do I think it's feasible/desirable to change the compiler to enable that pattern? I have no clue. The borrow checker is one of the most complex pieces of the Rust compiler and I'm not qualified to give you an answer on that. This seems related to (and might even be a subset of) the discussion on non-lexical borrow scopes, so I encourage you to look into it and possibly contribute if you're interested in this topic.
For the time being I'd suggest just returning a clone instead of a reference, if possible. I assume returning an Err is not the typical case, so performance should not be a particular worry, but I'm not sure how the K:Clone bound might work with the types you're using.
impl <K, V> MyArray<K, V> where K:Clone + Ord { // 1. now K is also Clone
pub fn insert_largest(&mut self, k: K, v: V) ->
Result<(), K> { // 2. returning K (not &K)
match self.0.iter().next_back() {
None => (),
Some(&(ref lk, _)) => {
if lk > &k {
return Err(lk.clone()); // 3. returning a clone
}
}
};
self.0.push((k, v));
Ok(())
}
}
Why does returning early not finish outstanding borrows?
Because the current implementation of the borrow checker is overly conservative.
Your code works as-is once non-lexical lifetimes are enabled, but only with the experimental "Polonius" implementation. Polonius is what enables conditional tracking of borrows.
I've also simplified your code a bit:
#![feature(nll)]
struct MyArray<K, V>(Vec<(K, V)>);
impl<K: Ord, V> MyArray<K, V> {
pub fn insert_largest(&mut self, k: K, v: V) -> Result<(), &K> {
if let Some((lk, _)) = self.0.iter().next_back() {
if lk > &k {
return Err(lk);
}
}
self.0.push((k, v));
Ok(())
}
}
I have to iterate on keys, find the value in HashMap by key, possibly do some heavy computation in the found struct as a value (lazy => mutate the struct) and cached return it in Rust.
I'm getting the following error message:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:25:26
|
23 | fn it(&mut self) -> Option<&Box<Calculation>> {
| - let's call the lifetime of this reference `'1`
24 | for key in vec!["1","2","3"] {
25 | let result = self.find(&key.to_owned());
| ^^^^ `*self` was mutably borrowed here in the previous iteration of the loop
...
28 | return result
| ------ returning this value requires that `*self` is borrowed for `'1`
Here is the code in playground.
use std::collections::HashMap;
struct Calculation {
value: Option<i32>
}
struct Struct {
items: HashMap<String, Box<Calculation>> // cache
}
impl Struct {
fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
None // find, create, and/or calculate items
}
fn it(&mut self) -> Option<&Box<Calculation>> {
for key in vec!["1","2","3"] {
let result = self.find(&key.to_owned());
if result.is_some() {
return result
}
}
None
}
}
I can't avoid the loop as I have to check multiple keys
I have to make it mutable (self and the structure) as the possible calculation changes it
Any suggestion on how to change the design (as Rust forces to think in a bit different way that makes sense) or work around it?
PS. There are some other issues with the code, but let's split the problems and solve this one first.
You can't do caching with exclusive access. You can't treat Rust references like general-purpose pointers (BTW: &String and &Box<T> are double indirection, and very unidiomatic in Rust. Use &str or &T for temporary borrows).
&mut self means not just mutable, but exclusive and mutable, so your cache supports returning only one item, because the reference it returns has to keep self "locked" for as long as it exists.
You need to convince the borrow checker that the thing that find returns won't suddenly disappear next time you call it. Currently there's no such guarantee, because the interface doesn't stop you from calling e.g. items.clear() (borrow checker checks what function's interface allows, not what function actually does).
You can do that either by using Rc, or using a crate that implements a memory pool/arena.
struct Struct {
items: HashMap<String, Rc<Calculation>>,
}
fn find(&mut self, key: &str) -> Rc<Calculation>
This way if you clone the Rc, it will live for as long as it needs, independently of the cache.
You can also make it nicer with interior mutability.
struct Struct {
items: RefCell<HashMap<…
}
This will allow your memoizing find method to use a shared borrow instead of an exclusive one:
fn find(&self, key: &str) -> …
which is much easier to work with for the callers of the method.
Probably not the cleanest way to do that, but it compiles. The idea is not to store the value found in a temporary result, to avoid aliasing: if you store the result, self is kept borrowed.
impl Struct {
fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
None
}
fn it(&mut self) -> Option<&Box<Calculation>> {
for key in vec!["1","2","3"] {
if self.find(&key.to_owned()).is_some() {
return self.find(&key.to_owned());
}
}
None
}
}
I had similar issues, and I found a workaround by turning the for loop into a fold, which convinced the compiler that self was not mutably borrowed twice.
It works without using interior mutability or duplicated function call; the only downside is that if the result was found early, it will not short-circuit but continue iterating until the end.
Before:
for key in vec!["1","2","3"] {
let result = self.find(&key.to_owned());
if result.is_some() {
return result
}
}
After:
vec!["1", "2,", "3"]
.iter()
.fold(None, |result, key| match result {
Some(result) => Some(result),
None => self.find(&key.to_string())
})
Working playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=92bc73e4bac556ce163e0790c7d3f154
Here's a Thing:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
And here's a function that tries to mutate a Thing and returns either true or false, depending on if a Thing is available:
fn try_increment(handle: Option<&mut Thing>) -> bool {
if let Some(t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
Here's a sample of usage:
fn main() {
try_increment(None);
let mut thing = Thing(0);
try_increment(Some(&mut thing));
try_increment(Some(&mut thing));
try_increment(None);
}
As written, above, it works just fine (link to Rust playground). Output below:
warning: increment failed
incremented: 1
incremented: 2
warning: increment failed
The problem arises when I want to write a function that mutates the Thing twice. For example, the following does not work:
fn try_increment_twice(handle: Option<&mut Thing>) {
try_increment(handle);
try_increment(handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
The error makes perfect sense. The first call to try_increment(handle) gives ownership of handle away and so the second call is illegal. As is often the case, the Rust compiler yields a sensible error message:
|
24 | try_increment(handle);
| ------ value moved here
25 | try_increment(handle);
| ^^^^^^ value used here after move
|
In an attempt to solve this, I thought it would make sense to pass handle by reference. It should be an immutable reference, mind, because I don't want try_increment to be able to change handle itself (assigning None to it, for example) only to be able to call mutations on its value.
My problem is that I couldn't figure out how to do this.
Here is the closest working version that I could get:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
// PROBLEM: this line is allowed!
// (*handle) = None;
if let Some(ref mut t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
This code runs, as expected, but the Option is now passed about by mutable reference and that is not what I want:
I'm allowed to mutate the Option by reassigning None to it, breaking all following mutations. (Uncomment line 12 ((*handle) = None;) for example.)
It's messy: There are a whole lot of extraneous &mut's lying about.
It's doubly messy: Heaven only knows why I must use ref mut in the if let statement while the convention is to use &mut everywhere else.
It defeats the purpose of having the complicated borrow-checking and mutability checking rules in the compiler.
Is there any way to actually achieve what I want: passing an immutable Option around, by reference, and actually being able to use its contents?
You can't extract a mutable reference from an immutable one, even a reference to its internals. That's kind of the point! Multiple aliases of immutable references are allowed so, if Rust allowed you to do that, you could have a situation where two pieces of code are able to mutate the same data at the same time.
Rust provides several escape hatches for interior mutability, for example the RefCell:
use std::cell::RefCell;
fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
if let Some(t) = handle {
t.borrow_mut().increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(handle: Option<RefCell<Thing>>) {
try_increment(&handle);
try_increment(&handle);
}
fn main() {
let mut thing = RefCell::new(Thing(0));
try_increment_twice(Some(thing));
try_increment_twice(None);
}
TL;DR: The answer is No, I can't.
After the discussions with #Peter Hall and #Stargateur, I have come to understand why I need to use &mut Option<&mut Thing> everywhere. RefCell<> would also be a feasible work-around but it is no neater and does not really achieve the pattern I was originally seeking to implement.
The problem is this: if one were allowed to mutate the object for which one has only an immutable reference to an Option<&mut T> one could use this power to break the borrowing rules entirely. Concretely, you could, essentially, have many mutable references to the same object because you could have many such immutable references.
I knew there was only one mutable reference to the Thing (owned by the Option<>) but, as soon as I started taking references to the Option<>, the compiler no longer knew that there weren't many of those.
The best version of the pattern is as follows:
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
if let Some(ref mut t) = handle {
t.increment_self();
true
}
else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
Notes:
The Option<> holds the only extant mutable reference to the Thing
try_increment_twice() takes ownership of the Option<>
try_increment() must take the Option<> as &mut so that the compiler knows that it has the only mutable reference to the Option<>, during the call
If the compiler knows that try_increment() has the only mutable reference to the Option<> which holds the unique mutable reference to the Thing, the compiler knows that the borrow rules have not been violated.
Another Experiment
The problem of the mutability of Option<> remains because one can call take() et al. on a mutable Option<>, breaking everything following.
To implement the pattern that I wanted, I need something that is like an Option<> but, even if it is mutable, it cannot be mutated. Something like this:
struct Handle<'a> {
value: Option<&'a mut Thing>,
}
impl<'a> Handle<'a> {
fn new(value: &'a mut Thing) -> Self {
Self {
value: Some(value),
}
}
fn empty() -> Self {
Self {
value: None,
}
}
fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
if let Some(ref mut v) = self.value {
Some(mutation(v))
}
else {
None
}
}
}
Now, I thought, I can pass around &mut Handle's all day long and know that someone who has a Handle can only mutate its contents, not the handle itself. (See Playground)
Unfortunately, even this gains nothing because, if you have a mutable reference, you can always reassign it with the dereferencing operator:
fn try_increment(handle: &mut Handle) -> bool {
if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
// This breaks future calls:
(*handle) = Handle::empty();
true
}
else {
println!("warning: increment failed");
false
}
}
Which is all fine and well.
Bottom line conclusion: just use &mut Option<&mut T>
I have a Context struct:
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> &str {
&self.name
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
let name = context.get_name();
if name == "foo" {
context.set_foo(4);
}
}
In a function, I need to first get the name of the context and update foo according to the name I have:
let name = context.get_name();
if (name == "foo") {
context.set_foo(4);
}
The code won't compile because get_name() takes &self and set_foo() takes &mut self. In other words, I have an immutable borrow for get_name() but I also have mutable borrow for set_foo() within the same scope, which is against the rules of references.
At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
The error looks like:
error[E0502]: cannot borrow `context` as mutable because it is also borrowed as immutable
--> src/main.rs:22:9
|
20 | let name = context.get_name();
| ------- immutable borrow occurs here
21 | if name == "foo" {
22 | context.set_foo(4);
| ^^^^^^^ mutable borrow occurs here
23 | }
24 | }
| - immutable borrow ends here
I'm wondering how can I workaround this situation?
This is a very broad question. The borrow checker is perhaps one of the most helpful features of Rust, but also the most prickly to deal with. Improvements to ergonomics are being made regularly, but sometimes situations like this happen.
There are several ways to handle this and I'll try and go over the pros and cons of each:
I. Convert to a form that only requires a limited borrow
As you learn Rust, you slowly learn when borrows expire and how quickly. In this case, for instance, you could convert to
if context.get_name() == "foo" {
context.set_foo(4);
}
The borrow expires in the if statement. This usually is the way you want to go, and as features such as non-lexical lifetimes get better, this option gets more palatable. For instance, the way you've currently written it will work when NLLs are available due to this construction being properly detected as a "limited borrow"! Reformulation will sometimes fail for strange reasons (especially if a statement requires a conjunction of mutable and immutable calls), but should be your first choice.
II. Use scoping hacks with expressions-as-statements
let name_is_foo = {
let name = context.get_name();
name == "foo"
};
if name_is_foo {
context.set_foo(4);
}
Rust's ability to use arbitrarily scoped statements that return values is incredibly powerful. If everything else fails, you can almost always use blocks to scope off your borrows, and only return a non-borrow flag value that you then use for your mutable calls. It's usually clearer to do method I. when available, but this one is useful, clear, and idiomatic Rust.
III. Create a "fused method" on the type
impl Context {
fn set_when_eq(&mut self, name: &str, new_foo: i32) {
if self.name == name {
self.foo = new_foo;
}
}
}
There are, of course, endless variations of this. The most general being a function that takes an fn(&Self) -> Option<i32>, and sets based on the return value of that closure (None for "don't set", Some(val) to set that val).
Sometimes it's best to allow the struct to modify itself without doing the logic "outside". This is especially true of trees, but can lead to method explosion in the worst case, and of course isn't possible if operating on a foreign type you don't have control of.
IV. Clone
let name = context.get_name().clone();
if name == "foo" {
context.set_foo(4);
}
Sometimes you have to do a quick clone. Avoid this when possible, but sometimes it's worth it to just throw in a clone() somewhere instead of spending 20 minutes trying to figure out how the hell to make your borrows work. Depends on your deadline, how expensive the clone is, how often you call that code, and so on.
For instance, arguably excessive cloning of PathBufs in CLI applications isn't horribly uncommon.
V. Use unsafe (NOT RECOMMENDED)
let name: *const str = context.get_name();
unsafe{
if &*name == "foo" {
context.set_foo(4);
}
}
This should almost never be used, but may be necessary in extreme cases, or for performance in cases where you're essentially forced to clone (this can happen with graphs or some wonky data structures). Always, always try your hardest to avoid this, but keep it in your toolbox in case you absolutely have to.
Keep in mind that the compiler expects that the unsafe code you write upholds all the guarantees required of safe Rust code. An unsafe block indicates that while the compiler cannot verify the code is safe, the programmer has. If the programmer hasn't correctly verified it, the compiler is likely to produce code containing undefined behavior, which can lead to memory unsafety, crashes, etc., many of the things that Rust strives to avoid.
There is problably some answer that will already answer you, but there is a lot of case that trigger this error message so I will answer your specific case.
The easier solution is to use #![feature(nll)], this will compile without problem.
But you could fix the problem without nll, by using a simple match like this:
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
match context.get_name() {
"foo" => context.set_foo(4),
// you could add more case as you like
_ => (),
}
}
Prior to seeing #Stargateur's comment, I came up with the below which compiles fine, but does clone the name string:
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> String {
self.name.clone()
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: String::from("bill"),
foo: 5,
};
let name = context.get_name();
if name == "foo" {
context.set_foo(4);
}
println!("Hello World!");
}
Working with #Stargateur's sample, it turns out there is a surprisingly simple solution to this particular problem - combine with the get_name with the if, e.g.
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> &String {
&self.name
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
if context.get_name() == "foo" {
context.set_foo(4);
}
}
I believe that this is because the variable for the get_name part has its lifetime clearly delineated, whereas when the name variable was separate, it essentially could have its value suddenly changed without an explicit mutation.
I was under the impression that mutable references (i.e. &mut T) are always moved. That makes perfect sense, since they allow exclusive mutable access.
In the following piece of code I assign a mutable reference to another mutable reference and the original is moved. As a result I cannot use the original any more:
let mut value = 900;
let r_original = &mut value;
let r_new = r_original;
*r_original; // error: use of moved value *r_original
If I have a function like this:
fn make_move(_: &mut i32) {
}
and modify my original example to look like this:
let mut value = 900;
let r_original = &mut value;
make_move(r_original);
*r_original; // no complain
I would expect that the mutable reference r_original is moved when I call the function make_move with it. However that does not happen. I am still able to use the reference after the call.
If I use a generic function make_move_gen:
fn make_move_gen<T>(_: T) {
}
and call it like this:
let mut value = 900;
let r_original = &mut value;
make_move_gen(r_original);
*r_original; // error: use of moved value *r_original
The reference is moved again and therefore the program behaves as I would expect.
Why is the reference not moved when calling the function make_move?
Code example
There might actually be a good reason for this.
&mut T isn't actually a type: all borrows are parametrized by some (potentially inexpressible) lifetime.
When one writes
fn move_try(val: &mut ()) {
{ let new = val; }
*val
}
fn main() {
move_try(&mut ());
}
the type inference engine infers typeof new == typeof val, so they share the original lifetime. This means the borrow from new does not end until the borrow from val does.
This means it's equivalent to
fn move_try<'a>(val: &'a mut ()) {
{ let new: &'a mut _ = val; }
*val
}
fn main() {
move_try(&mut ());
}
However, when you write
fn move_try(val: &mut ()) {
{ let new: &mut _ = val; }
*val
}
fn main() {
move_try(&mut ());
}
a cast happens - the same kind of thing that lets you cast away pointer mutability. This means that the lifetime is some (seemingly unspecifiable) 'b < 'a. This involves a cast, and thus a reborrow, and so the reborrow is able to fall out of scope.
An always-reborrow rule would probably be nicer, but explicit declaration isn't too problematic.
I asked something along those lines here.
It seems that in some (many?) cases, instead of a move, a re-borrow takes place. Memory safety is not violated, only the "moved" value is still around. I could not find any docs on that behavior either.
#Levans opened a github issue here, although I'm not entirely convinced this is just a doc issue: dependably moving out of a &mut reference seems central to Rust's approach of ownership.
It's implicit reborrow. It's a topic not well documented.
This question has already been answered pretty well:
how implicit reborrow works
how reborrow works along with borrow split
If I tweak the generic one a bit, it would not complain either
fn make_move_gen<T>(_: &mut T) {
}
or
let _ = *r_original;