How does multiple mutable reference prevention work in Rust? - rust

Why it is allowed to do something like this:
fn main() {
let mut w = MyStruct;
w.fun1();
}
struct MyStruct;
impl MyStruct {
fn fun1(&mut self) {
self.fun2();
}
fn fun2(&mut self) {
println!("Hello world 2");
}
}
In the above code fun1() gets mut MyStruct and calls fun2() also with mut MyStruct. Is it double mutable reference in one scope?

This is allowed because the borrow checker can conclude there is only one mutable reference being accessed during execution. While fun2 is running, no other statement in fun1 is being executed. When the next statement in fun1 (if there was any) starts executing, fun2 has already dropped its mutable reference.
In the other question linked:
fn main() {
let mut x1 = String::from("hello");
let r1 = &mut x1;
let r2 = &mut x1;
r1.insert(0, 'w');
}
We can say r2 is never used, but borrow checker decided it shouldn't be allowed. Consider this example:
fn main() {
let mut x1 = String::from("hello");
let r1 = &mut x1;
r1.insert(0, 'w');
let r2 = &mut x1;
r2.insert(0, 'x');
}
This compiles and runs correctly. I suppose borrow checker assumes the lifetime r1 ends before r2 is created. If this makes sense, calling methods that mutate self shouldn't be so surprising.
(I don't know why the 1st piece of code does not compile, but I am glad rust team made it that way. r2 should not be there anyway.)

Related

Why does this Ownership transfer does not cause an error?

I'm working through the ownership section.
I wrote the following code an expected an error.
Question: Why isn't there an error in the first compiling version?
This one compiles:
fn main () {
let mut x = String::from("vier");
let mut y = x.to_owned()+"1"; //here x should go out of scope, because used in a method
let mut z = x.to_owned()+"2";
x.push_str("test");
println!("{}", x);
println!("{}", y);
println!("{}", z);
}
This one does not compile:
fn main () {
let mut x = String::from("vier");
let mut y = x;//x out of scope
let mut z = x;//x out of scope
x.push_str("test");
println!("{}", x);
println!("{}", y);
println!("{}", z);
}
to_owned() only takes a reference, not ownership. You'll notice that the blanket implementation for T requires T: Clone:
impl<T> ToOwned for T where
T: Clone,
type Owned = T
That indicates that .to_owned() uses .clone() for any type that implements Clone.
to_owned is a method from borrow::ToOwned trait. Which returns an owned version (cloned/copy) of the element. It just takes an &self so the object is not really moved.

how to constrain the lifetime when doing unsafe conversion

I want to create a Test ref from the array ref with the same size and keep the lifetime checking.
I can do this by using a function and I know the function can deduce the lifetime. The code below is intentionally designed to fail when compiling because of use after move. It works.
struct Test {
a: i32,
}
/// 'a can be removed for simplification
fn create_test<'a>(ptr: &'a mut [u8]) -> &'a mut Test {
assert_eq!(ptr.len(), size_of::<Test>());
unsafe { &mut *(ptr as *mut [u8] as *mut Test) }
}
fn main() {
let mut space = Box::new([0 as u8; 100]);
let (s1, _s2) = space.split_at_mut(size_of::<Test>());
let test = create_test(s1);
drop(space);
test.a += 1;
}
My question is how can I do this without declaring an extra function to constrain the lifetime.
fn main() {
let mut space = Box::new([0 as u8; 100]);
let (s1, _s2): (&'a mut [u8], _) = space.split_at_mut(size_of::<Test>());
let test: &'a mut Test = unsafe { &mut *(s1 as *mut [u8] as *mut Test) };
drop(space);
}
such `a is not allowed.
The following code works. And it holds the borrowing check.
fn main() {
let mut space = Box::new([0 as u8; 100]);
let layout = Layout::new::<Test>();
println!("{}", layout.align());
let (_prefix, tests, _suffix) = unsafe { space.align_to_mut::<Test>() };
assert!(tests.len() > 0);
let test = &mut tests[0];
let (_, suffix, _) = unsafe { tests[1..].align_to_mut::<u8>() };
}
You cannot do that, but this is not needed either. Lifetimes are used to ensure safety across function boundaries. In the same function you can just ensure safety manually.
Theoretically, we would not need a borrow checker if the compiler could just inspect the called functions and follow the execution path to deterime whether we invoke Undefined Behavior. Practically, this can't be done because of problems like the Halting Problem and performance.

How can I borrow from a HashMap to read and write at the same time?

I have a function f that accepts two references, one mut and one not mut. I have values for f inside a HashMap:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
{
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
}
println!("Results: {:?}", map)
}
This doesn't work because HashMap::get and HashMap::get_mut attempt to mutably borrow and immutably borrow at the same time:
error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
--> src/main.rs:15:27
|
12 | let a: &i32 = map.get("1").unwrap();
| --- immutable borrow occurs here
...
15 | let b: &mut i32 = map.get_mut("2").unwrap();
| ^^^ mutable borrow occurs here
...
18 | }
| - immutable borrow ends here
In my real code I'm using a large, complex structure instead of a i32 so it is not a good idea to clone it.
In fact, I'm borrowing two different things mutably/immutably, like:
struct HashMap {
a: i32,
b: i32,
}
let mut map = HashMap { a: 1, b: 2 };
let a = &map.a;
let b = &mut map.b;
Is there any way to explain to the compiler that this is actually safe code?
I see how it possible to solve in the concrete case with iter_mut:
{
let mut a: &i32 = unsafe { mem::uninitialized() };
let mut b: &mut i32 = unsafe { mem::uninitialized() };
for (k, mut v) in &mut map {
match *k {
"1" => {
a = v;
}
"2" => {
b = v;
}
_ => {}
}
}
f(a, b);
}
But this is slow in comparison with HashMap::get/get_mut
TL;DR: You will need to change the type of HashMap
When using a method, the compiler does not inspect the interior of a method, or perform any runtime simulation: it only bases its ownership/borrow-checking analysis on the signature of the method.
In your case, this means that:
using get will borrow the entire HashMap for as long as the reference lives,
using get_mut will mutably borrow the entire HashMap for as long as the reference lives.
And therefore, it is not possible with a HashMap<K, V> to obtain both a &V and &mut V at the same time.
The work-around, therefore, is to avoid the need for a &mut V entirely.
This can be accomplished by using Cell or RefCell:
Turn your HashMap into HashMap<K, RefCell<V>>,
Use get in both cases,
Use borrow() to get a reference and borrow_mut() to get a mutable reference.
use std::{cell::RefCell, collections::HashMap};
fn main() {
let mut map = HashMap::new();
map.insert("1", RefCell::new(1));
map.insert("2", RefCell::new(2));
{
let a = map.get("1").unwrap();
println!("a: {}", a.borrow());
let b = map.get("2").unwrap();
println!("b: {}", b.borrow());
*b.borrow_mut() = 5;
}
println!("Results: {:?}", map);
}
This will add a runtime check each time you call borrow() or borrow_mut(), and will panic if you ever attempt to use them incorrectly (if the two keys are equal, unlike your expectations).
As for using fields: this works because the compiler can reason about borrowing status on a per-field basis.
Something appears to have changed since the question was asked. In Rust 1.38.0 (possibly earlier), the following compiles and works:
use std::collections::HashMap;
fn f(a: &i32, b: &mut i32) {}
fn main() {
let mut map = HashMap::new();
map.insert("1", 1);
map.insert("2", 2);
let a: &i32 = map.get("1").unwrap();
println!("a: {}", a);
let b: &mut i32 = map.get_mut("2").unwrap();
println!("b: {}", b);
*b = 5;
println!("Results: {:?}", map)
}
playground
There is no need for RefCell, nor is there even a need for the inner scope.

Why does Vec<T>::split_at_mut borrow the vector for the rest of the scope?

Vec<T> has two methods:
fn push(&mut self, value: T)
fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T])
They both take a mutable reference to the vector. But the scope of the borrow seems to be different, e.g:
fn works() {
let mut nums: Vec<i64> = vec![1,2,3,4];
nums.push(5);
println!("{}", nums.len());
}
fn doesnt_work() {
let mut nums: Vec<i64> = vec![1,2,3,4];
let (l,r) = nums.split_at_mut(2);
println!("{}", nums.len());
}
fn also_works() {
let mut nums: Vec<i64> = vec![1,2,3,4];
let _ = nums.split_at_mut(2);
println!("{}", nums.len());
}
The doesnt_work function doesn't compile, saying there is already a mutable borrow on nums and that it ends and the end of the function. The problem goes away if I ignore the values returned from split_at_mut.
The borrowing of nums in doesnt_work will last as long as the variables l and r exist because the values in the vector (and the vector itself) have literally been borrowed and are now accessible only through l and r.
You can see this effect by putting the let for l and r in a scope which ends so the borrow also ends. For example this code works fine but if you try to move the println! inside the scope (inside the curly brackets) then it will fail:
fn works() {
let mut nums = vec![1,2,3,4];
{
let (l, r) = nums.split_at_mut(2);
//println!("{}", nums.len()); //println! will fail here
}
println!("{}", nums.len());
}
In your also_works example you don't do anything with the result so the borrow is lost immediately. Basically the compiler can see that there is no way you can access the vector through the result of the method so you're free to access them through the original vector.
Let me answer my own question, since what I was really missing were lifetimes. This code compiles:
fn maybe_use<'a, 'b>(v1: &'a mut Vec<i64>, v2: &'b mut Vec<i64>) -> &'a mut Vec<i64> {
v1
}
fn main() {
let mut nums1: Vec<i64> = vec![1,2,3,4];
let mut nums2: Vec<i64> = vec![1,2,3,4];
let ret = maybe_use(&mut nums1, &mut nums2);
println!("{}", nums2.len());
}
Because the return type of maybe_use makes it clear the reference comes from the first argument. If we change v2 to use 'a lifetime, main stops compiling because both vectors passed to maybe_use are considered borrowed. If we omit the lifetime altogether, compiler emits this error:
this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from v1 or v2
So what surprised me initially (how does the compiler know split_at_mut returns pointers to the vector?) boils down to the references having the same lifetime.

Reference passed as parameter is not moved

fn t(x: &mut u8) -> &mut u8 {
x
}
fn main() {
let mut x = 5u8;
let y = & mut x;
let z = t(y);
println!("{}", y);
}
Compiling this gives me this error:
main.rs:9:20: 9:21 error: cannot borrow `y` as immutable because `*y` is also borrowed as mutable
main.rs:9 println!("{}", y);
I would have thought y would have been moved during the call to t and then back to z, resulting in an error: use of moved value
Why do I get this error message instead?
Does Rust automatically create a new borrow instead of passing ownership when references are offered as function parameters?
What is the purpose of this behaviour?
You are returning a mutable reference to the parameter from your function. However, Rust doesn't know that the method hasn't kept a copy of that pointer didn't return a subsection of that pointer, were it a struct. This means that at any time, the value pointed to might be changed, which is a big no-no in Rust; if it were allowed, then you could easily cause memory errors.
Does Rust automatically create a new borrow
Yes, Rust "re-borrows" references.
A better example requires a smidge more complexity:
struct Thing { a: u8, b: u8 }
fn t(x: &mut Thing) -> &mut u8 {
&mut x.a
}
fn main() {
let mut x = Thing { a: 5, b: 6 };
let z = t(&mut x);
*z = 0;
// x.a = 0; // cannot assign to `x.a` because it is borrowed
}
Here, t returns a mutable pointer to a subset of the struct. This means that the entire struct is borrowed, and we cannot change it (except via z). Rust applies this logic to all functions, and doesn't try to recognize that your t function just returns the same pointer.
By compiling your program with rustc --pretty=expanded, we can see that the println! macro borrows its argument:
#![no_std]
#[macro_use]
extern crate "std" as std;
#[prelude_import]
use std::prelude::v1::*;
fn t(x: &mut u8) -> &mut u8 { x }
fn main() {
let mut x = 5u8;
let y = &mut x;
let z = t(y);
::std::io::stdio::println_args(::std::fmt::Arguments::new({
#[inline]
#[allow(dead_code)]
static __STATIC_FMTSTR:
&'static [&'static str]
=
&[""];
__STATIC_FMTSTR
},
&match (&y,) { // <----- y is borrowed here
(__arg0,)
=>
[::std::fmt::argument(::std::fmt::String::fmt,
__arg0)],
}));
}

Resources