Why can't we have multiple &mut


#21

Sorry to ask too much but can I see it via code example cause I don’t know what deref coercion is?


#22

I tried doing that but it ended up not allowing me to build my project at all, do I have to install the updated compiler or something?


#23

Probably, just do rustup update on the command line.


#24

Probably the most common way to get a &str from a &String is to call one of these methods. They are actually implemented for str, but you can call them on a String because String implements Deref<Target=str>.

let mut s1 = String::from("  test  ");

let s = &s1;

let t = s.trim();

s1.push_str("Testing");

In this snippet, t points to a portion ("test") of s1. In Rust 2018, this is fine, because the borrows for s and t both end before s1.push_str("Testing"). But in the previous edition this is an error because borrows endure to the end of the lexical scope.

It will become an error in Rust 2018, too, if you add a usage of t after push_str:

println!("{}", t);

This must be disallowed because t still points to the original string, but push_str may have caused s1 to be reallocated and t to become dangling.


#25

You actually could have multiple mut, however imagine situation that part of object behind one mutable reference was destroyed, while another mut ref used to change it, it will lead to SEGFAULT in worst cases to array out-of-bound vulnerability, and for this reason double aliases mut reference is considered as error state.

Some set of algorithm are requires to have an ability for multiple mut pointers to the same object, and for this reason you should consider use safe wrappers for that like RC, ARC, RefCells. Or you could use raw mut pointer, while dereferencing of pointer considered as unsafe operation, that could lead to SEGFAULT crash, out-of-boundaries volnurability described above, and since it is unsafe compiler can’t guarantee proper validation against SEGFAULT for you.


#26

Can I try to understand one thing.
image

in this example, is s pointing to s1 or is s pointing to the contents stored on the heap?


#27

I see, thanks for clearing that up for me.


#28

It’s pointing at s1 because s is &String.


#29

image

This is 2018 version and this is now permitted, right.

But if I were to print out s instead of s1 I will get an error cause as soon as I did s1.push_str(" 123"); it made s invalid, right?

Now I am aware that s is pointing to s1. But why does s become invalid if I added data to the heap? s1's pointer might update if the location of the heap was moved, but s's pointer remains the same as it is pointing to s1.

Forgive me for asking similar questions but I just don’t understand.


#30

Correct.

No need to ask for forgiveness - this is what the forum is for :slight_smile:. This is actually a good question.

I think you’re taking the example a bit too literally, so to speak, by focusing on String and how it works internally. As far as the compiler is concerned, push_str takes a &mut self borrow. To get that kind of borrow, you must have exclusive access to the value (String in this case, but again, think more generally - pretend it’s some opaque type whose internals you don’t know).

Imagine printing a String also involved printing its length and/or capacity, both of which are fields of the String type (well, of the internal Vec<u8> to be precise, but again, that’s impl details). The compiler is allowed to assume that, if you hold an immutable reference, then nothing can mutate the object during that time. Now, this isn’t strictly true due to interior mutability, but those use special types that inform the compiler that it can’t make that assumption. But String doesn’t use those interior mutability types, so let’s continue with this example. So if it knows that length/capacity cannot change, it can make some perf optimizations, such as reading the length and capacity fields and holding them in CPU registers. push_str in this example will invalidate at least the length field, and possibly the capacity as well (if the heap storage had to be reallocated). So if you were to print s, it’s possible such code would miscompile because it would use stale len/capacity values. For printing this may not seem so dire, but that’s because we’re using stale values “just” for printing - if you, instead, have code that uses the stale values as indices into raw memory or forms pointers from them or so on, you can easily create security issues (for example).

So you can think of taking a &mut as an invalidation step of all previous immutable borrows - they’re no longer valid after that. How exactly are they invalid? We don’t know - it’ll vary based on the concrete type in question.


#31

Thanks bro :slight_smile:

How would s use stale len/capacity values if it is pointing to s1? Wouldn’t s only store the ptr value that points to s1?


#33

s doesn’t store the length and the capacity, it only stores the ptr which points to s1 so I am not too sure what do you mean by your example?


#34

Length and capacity are reachable from s, and that’s what matters; imagine if printing a String, which would be triggered if you did println!("{}", s) in your example, looked at the length/capacity fields. This “reachability path” is quite important in all of this - if you hold a mutable borrow of some value v, then you implicitly hold a mutable borrow on all data reachable from v. Conversely, this is also what allows disjointness to work, such that you can hold &v.field1 and &mut v.field2 concurrently - the compiler understands that you’re holding disjoint (sibling, in this case) data, and allows this because you can’t invalidate v.field1 via v.field2.


#35

Yes I am well aware of that, s points to s1 and s1 stores the ptr, len and cap. but when I did this s1.push_str(" 123"); and the s1's ptr, len and cap changed, wouldn’t s still not be affected as when I run this println!("{}", s); all s has to do is point to s1 and retrieve its ptr, len and cap, right? And s1 has already updated its values, right?


#36

It doesn’t matter what you know, it matters what the compiler knows, and what it’s allowed to do.

All the compiler knows is that mutable and immutable borrows cannot coexist, end of discussion. Whether this particular case would or would not work in practice is irrelevant.


#37

But s is an immutable borrow, so tells the compiler that all data accessed via s will not change during the life of s. When s1 reallocates, that promise is broken. It’s incorrect assumptions like that which give malware its foothold.


#38

I see.

Is it s1 that moves or is it the stuff that is stored on the heap?


#39

The stuff on the heap moves, which is reflected in the three fields within s1. So if the compiler follows s and loads one of those fields in a register, and after that your code modifies s1, and then the compiler uses the now-out-of-date value in the register (because it didn’t know that you lied to it about the value not changing), you have [Edit]:UB use-after-free. That’s what gives malware its foothold; a malware author analyzes the binary machine instructions, sees that an inconsistent use is occurring, then manipulates values so that their desired unintended-by-the-author result is obtained.


#40

Thanks for clearing that up for me. Is a register the CPU’s memory where it stores cached stuff?


#41

Registers are immediately-accessible storage that has a direct path to processing facilities on the same chip. Registers are high speed but necessarily very limited in number due to their impact on chip size, cost and power consumption.

Cache is a mechanism for improving the access-time statistics for much-larger much-much-slower memories for which the cache functions as a local limited-capacity surrogate. Modern cache systems usually are in hierarchies, from small, fast caches that are on the same chip as the processor(s), to intermediary caches that are larger and slower, to main memory that is again much larger and slower.

You should google “computer architecture” and “processor registers” and such to gain a basic understanding of the “bare metal” on which programs run. Here are two such links, in this case from Wikipedia: processor registers and a few-years-old ARM architecture.