The Un-Sandwiching rule
All references and reference related problems follow the "un-sandwiching"
rule.
The "un-sandwiching" rule states that:
Given a value, you cannot use a mutable reference between an immutable reference's declaration "zone" and immutable reference's usage "zone". Also, you cannot use mutable or immutable reference between a mutable's declaration zone and mutable's usage zone.
Note:
"Un-Sandwiching rule" is just a made up name that I created to better explain the quirks of immutable and mutable references. Don't use this name in forums!
What does it mean?
This rule is a mashed and simplified form of:
At any given time, you can have either one mutable reference or any number of immutable references. References must always be valid.
(I agree that the "un-sandwiching" rule looks more lengthy, but it will help
you to simplify things.)
Explaination of the rule
The rule can be broken down into two parts.
You cannot use a mutable reference while you are between the immutable reference's declaration and usage zone. However, you may have unlimited immutable references of the same value.
You cannot have any reference (immutable or mutable), if you are between a mutable reference's declaration and usage zone.
A demonstration
fn main() {
let mut a = String::from("hello world");
let immut_ref = &a; // <- declaration zone
/*
This is the "un-sandwiching" zone. You can have unlimited immutable
references to `a`, directly and indirectly (more on that below), but
*NOT A SINGLE MUTABLE REFERENCE IS ALLOWED*.
*/
println!("{}", immut_ref); // <- usage zone
}
What is allowed?
You may have these types of references between that zone (not an exhaustive list):
let b = &a; // (direct) *immutable* reference to `a`
let c = &b; // (indirect) *immutable* reference to `b` (as `b` is immutable too)
let d = &(*b); // (indirect) *immutable* reference to `a` via `b`
What is not allowed?
These (or any variations of these) are illegal:
let b_mut = &mut a; // You can see why. It clearly violates the "un-sandwiching"
// rule as we are creating a mutable reference between the
// declaration and usage zone.
a.push_str("breeze"); // Cannot do this because `a` is an "immutable" now.
// More on this below.
Let us look at the a.push_str("breeze")
statement at a more granular level.
The signature of push_str
is:
fn push_str(&mut self, string: &str) {
// ...
}
Can you see what's going on here?
In the signature, we have a &mut self
, which basically means &mut a
(since self
is a
). But going back to the "un-sandwiching" rule, we see that we cannot have a mutable reference in the declaration-usage zone.
What do I mean by a
becomes "immutable"?
I say a
becomes "immutable" because you cannot mutate a
as long as you are in the "un-sandwiching" zone. You can only have multiple immutable references.
What if we have multiple immutable references?
Let's say we have:
fn main() {
let mut a = String::from("great breeze");
let b = &a;
// use `b`
// use some more
// ...
let c = &a;
// use `c`
// use `b`
// ...
let d = &c;
println!("{}", b); // last usage of `b`.
// use `d`
// use `c`
do_something(d); // last usage of `d`
// use `c`
// ...
do_something_else(c); // last usage of `c`
}
Whew! That's a lot of jumbled references! Let's see this through our declaration-usage lens.
b
│
│
│ c
│ │
│ │
▼ │ d
~b │ │
│ │
│ │
│ ▼
│ ~d
│
│
▼
~c
The starting points are declarations, and the ~
s are the last usage of the references. Note that we don't care if a reference is a direct or an indirect one (eg. d
is an indirect reference).
From this diagram, we can clearly see that the "un-sandwiching" zone ranges from the first immutable reference b
, to the last usage of immutable reference c
. Between b
and ~c
, you cannot have any mutable reference.
And what about moves?
let mut a = String::from("some");
let c = &a;
let other = a; // is this possible?
println!("{}", c);
This is very simple to answer.
If the object cannot be copied, then you cannot perform a move within the zone.
Your homework
Deduce whether the following code is possible or not:
fn main() {
let mut a = String::from("mutable");
let b = &mut a; // a *mutable* reference
// ...
let c = &a; // 1. Is this possible?
// ...
println!("{}", b); // last usage
}
And what about this?
fn main() {
let mut a = String::from("mutable");
let b = &mut a; // a *mutable* reference
// ...
let d = &mut a; // 2. Is this possible?
// ...
println!("{}", b); // last usage
}
Explaination
-
let c = &a
is not possible.
By the "un-sandwiching" rule we cannot have a immutable reference while we are within a mutable reference's "un-sandwiching" zone.
-
let d = &mut a
is not possible.
You can see why. We are trying to create a mutable reference of the value while we are within the mutable reference's "un-sandwiching" zone. This is not allowed.
Conclusion
I hope this article helps you in understanding Rust a bit better.
Questions? Comments? Concerns? Please put them down below and I'd be happy to help you.
Image Source: Manjaro's /usr/share/backgrounds
folder 😃
Top comments (2)
Very good explanation.
It sure did, thank you very much!