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.