In Rust programming language, if a type T
implements trait Deref
, it can be used like a normal shared reference. By the same token, if a type T
implements trait DerefMut
, it can be used like a normal mutable reference. However, the following code may not behave what you expect.
use std::cell::{RefCell, RefMut};
fn main() {
let x: RefCell<String> = RefCell::new(String::from("hello"));
let y: RefMut<'_, String> = x.borrow_mut();
y.push_str("world");
println!("{y}");
}
Actually, this code does not compile!
$ cargo run
Compiling foo v0.1.0 (/tmp/foo)
error[E0596]: cannot borrow `y` as mutable, as it is not declared as mutable
--> src/main.rs:6:5
|
6 | y.push_str("world");
| ^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
5 | let mut y = x.borrow_mut();
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `foo` (bin "foo") due to 1 previous error
Rust compiler complains that y
is not mutable here. However, since RefMut
implements DerefMut
, we can use it like a &mut
, can't we? Just like the following code
fn main() {
let mut x: String = String::from("hello");
let y: &mut String = &mut x;
y.push_str("world");
println!("{y}");
}
Unfortunately, this is not the case. For smart pointers like y: RefMut
, if we write *y
, what actually happens behind the hood is this:
*(y.deref_mut());
deref_mut()
is the method defined by the trait DerefMut
, its method signature looks like this:
fn deref_mut(&mut self) -> &mut T
Everything makes sense at this moment. In the code at the very beginning, the Rust compiler complained that y
cannot be borrowed as mutable and suggested us adding the keyword mut
in front of y
. This makes sense because if y
is immutable, then it cannot be borrowed as a mutable reference. After applying the fix suggested by the compiler, everything goes smoothly.
use std::cell::{RefCell, RefMut};
fn main() {
let x: RefCell<String> = RefCell::new(String::from("hello"));
let mut y: RefMut<'_, String> = x.borrow_mut();
y.push_str("world");
println!("{y}");
}
$ cargo run
Compiling foo v0.1.0 (/tmp/foo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
Running `target/debug/foo`
helloworld
The lesson we learned here is that though smart pointers act like normal references, they are not references inherently. Be careful when interacting with them!
Comments NOTHING