let mut a="hello".to_string();
fn foo(s:&mut String){
drop(*s); // Take the ownership
*s="world".to_string();
}
In fact, Rust has the ability to detect the movement of the ownership, but now we can only using unsafe to do it.
let mut a="hello".to_string();
fn foo(s:&mut String){
unsafe{
drop(std::ptr::read(s)); // Take the ownership
// If we trying read `s` here it should cause an error
std::ptr::write(s,"world".to_string());
}
}
So why Rust doesn't support moving the value of mutable references away, then setting new value to the reference afterwards?
Surely std provided std::mem::replace and std::mem::swap, but sometimes they couldn't help.
Please think about the following example:
enum Foo{
Item1(String),
Item2(String)
}
fn convert_to_item2(foo:&mut Foo){
match foo{
Foo::Item2(_)=>{},
Foo::Item1(s)=>{
// How to change `foo` to `Foo::Item2` by taking `s`?
todo!();
}
}
}
And this one:
struct Bar{
a:String,
b:String
}
fn swap_a_and_b(bar:&mut Bar){
// How to swap field `a` and field `b` of `bar`?
todo!();
}
It's true that the enum case is more annoying, but there are essentially two ways:
// Take ownership of string by replacing it with an empty string.
fn convert_to_item2(foo: &mut Foo) {
match foo {
Foo::Item2(_) => {}
Foo::Item1(s) => {
let s = std::mem::take(s);
*foo = Foo::Item2(s);
}
}
}
// Replace foo with dummy value temporarily.
fn convert_to_item2(foo: &mut Foo) {
let dummy_value = Foo::Item1(String::new());
match std::mem::replace(foo, dummy_value) {
Foo::Item2(s) => {
*foo = Foo::Item2(s);
}
Foo::Item1(s) => {
*foo = Foo::Item2(s);
}
}
}
The reason it's annoying is that the compiler is worried about what happens if your program panics while you are replacing foo with a new value. If you have taken ownership of something without placing something else there and then panic, then the caller could catch the panic and observe foo having an invalid value.
In Rust unsafe is a perfectly valid tool, but it, basically, says to the compiler trust me, I know what I'm doing.
This makes everyone who reads your code a tiny bit nervous (now we need to walk the tightrope without safety belt), but sometimes it is the best solution.
This being said I would rather look on pure safe solution on playground or godbolt and would only reach out to unsafe if I can see that safe solution is ineffective.
There’s also replace_with which has a potentially more efficient implementation and nicer defaults, compared to take_mut. (It’s very explicit about the aborts; by default, it takes a closure that’s used to create a dummy value in case of panic.)
Well, the gist of the question is the same. It doesn't matter much whether you want to move out of a reference because you want to take the value of an enum or that of any other type.
This blog is vivid, but I still cannot get some points.
Firstly, won't a panic abort a thread immediately without doing anything else except printing panic information? If it is, why there is destruct runs after panics?
Secondly, since a panic can roughly understood as the panicked thread stop executing its task at one point permanently, other threads should not be able to access to the uninitialized memory via unreleased sync structs like Mutex, otherwise although there is no panic, there is also data race exists.
In general, I mean, I don't exactly know in what case we need to read that moved value after a panic.
Here's an example of examining some data during an unwinding panic. (If you're wondering why things unwind, it's so (a) destructors can do things like flush and close your files, leave the database in a sane state, etc, and (b) so they can be caught and recovered from in certain situations (e.g. async uses this a lot).)
Other Mutexes, like in the popular parking_lot, do simply unlock the Mutex on panic.
This happens when the guards are dropped (another use case for unwinding). Because the guard has been dropped, there is no data race if you obtain (guarded) access elsewhere.
The point is that reading the fields of the struct is possible in safe code, e.g. in the destructor or by something that caught the panic, another thread via Mutex, etc. So other safe code can't leave your struct in a partially uninitialized state, as that would lead to UB if the uninitialized field was read.