I think you misunderstand.
String
is not the mutable version of str
. It's its own type.
let mut x: String
is the mutable version of let x: String
.
String
is owned and can be modified.
str
is a "slice" type and refers to the content of a string, either inside of a String
, or as &'static str
in the global memory.
There is no mut str
because str
by definition is a reference to an immutable part of a string.
Let's look at your code. (renamed str
to s
because this got too confusing)
fn main() {
// Your variable `s` is `mut String`. It is a mutable string.
let mut s: String = String::from("hello");
// Your variable `slice` is a `&str`.
// It isn't mutable, it is a reference to a substring of `s`.
let slice: &str = &s[0..2]; // #1
// Here we already hold an immutable reference to `s` through the `slice` variable.
// This prevents us from modifying `s`, because you cannot reference an object mutably while
// it is borrowed immutably.
s.clear(); // #2
// This line is only important to force the variable `slice` to exist.
// Otherwise the compiler would be allowed to drop it before the `s.clear()` call,
// and everything would compile fine.
println!("{}", slice);
}
There is no &String
in there anywhere. Taking a slice of a String
via &s[0..2]
automatically creates a &str
instead, because that's what the specification of String
says:
fn index(&self, index: Range) -> &str
Why does it violate the principle above that seems to only apply to the reference with the same type?
This is incorrect. They do not have to be the same type. If you hold a &str
that references the content of a String
, then the String
object is also blocked from being mutated while the &str
reference exists. You can even store references in other objects and then the existance of those objects still block the original String
.
They are definitely different objects
This doesn't mean that they can't be connected.
To demonstrate that two objects of different types can have connected lifetimes, look at the following code:
#[derive(Debug)]
struct A {
pub value: u32,
}
#[derive(Debug)]
struct B<'a> {
pub reference: &'a u32,
}
impl A {
pub fn new(value: u32) -> Self {
Self { value }
}
pub fn set(&mut self, value: u32) {
self.value = value;
}
}
impl<'a> B<'a> {
pub fn new(a: &'a A) -> Self {
Self {
reference: &a.value,
}
}
}
fn main() {
let mut a = A::new(69);
println!("a: {:?}", a);
// Can be modified
a.set(42);
println!("a: {:?}", a);
// Create a B object that references the content of `a`
let b = B::new(&a);
println!("b: {:?}", b);
// While `b exists, it borrows a part of `a` (indicated through the fact that it has a lifetime type attached)
// That means, while `b` exists, `a` cannot be modified
a.set(420); // FAILS
// This ensures that `b` actually still exists
println!("b: {:?}", b);
}
The error message is quite clear:
error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable
--> src/main.rs:43:5
|
38 | let b = B::new(&a);
| -- immutable borrow occurs here
...
43 | a.set(420); // FAILS
| ^^^^^^^^^^ mutable borrow occurs here
...
46 | println!("b: {:?}", b);
| - immutable borrow later used here
Note that the B
type has a lifetime 'a
attached. This lifetime will automatically be derived by the compiler upon instantiation and is used to prevent mutable usage of the referenced A
object for as long as B
exists.
&str
also has a lifetime attached that is used to prevent mutable access of the referenced String
object.