Reputation: 109
Have I just forgotten how borrows and moves work?
let mut v = vec![1, 2, 3]; // I have some uncopyable value
if false {
let t = v; // I might do something that consumes it
}
println!("{:?}", v); // in some condition, I know for sure that I didn't consume it
Can I somehow use an unsafe
clause to tell the compiler to trust me?
Any solution must have no runtime overhead.
Upvotes: 5
Views: 1542
Reputation: 108800
The compiler won't let you access a variable you may have moved the value out of, even in unsafe code.
Some workarounds:
Wrap it in an Option
. You can then move the data out using the take
method, leaving a None
value behind.
This is the approach I recommend for local variables.
Replace the original vector by an empty vector. This is cheap, since empty vectors don't allocate.
let t = std::mem::replace(&mut v, Vec::new());
This is the closest equivalent to C++ moving, which is described as:
Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
Wrap it in ManuallyDrop
(this is safer than mem::forget
because it doesn't drop the value when a panic happens). Drop it manually at the end on the path where it's still initialized. Use deref to access it while it is still valid. ptr::read
to copy the value out, treating the original location as invalid/uninitialized.
This shouldn't have any runtime overhead, but I strongly recommend not using this on local variables. It's just not worth the complexity and risks.
use std::mem::ManuallyDrop;
use std::ptr;
fn main() {
let flag = //...;
unsafe {
let mut v = ManuallyDrop::new(vec![1, 2, 3]); // I have some uncopyable value
if flag {
let t = ptr::read(&*v); // I might do something that consumes it
// don't touch *v from now on
println!("{:?}", t);
}
if !flag {
println!("{:?}", *v); // in some condition, I know for sure that I didn't consume it
ManuallyDrop::drop(&mut v);
}
}
}
Upvotes: 11
Reputation: 3861
Just use mem::replace
, if you want to imitate C++ move semantics.
use std::mem;
let mut v = vec![1, 2, 3]; // I have some uncopyable value
if false {
let t = mem::replace(&mut v, vec![]); // I might do something that consumes it
}
println!("{:?}", v);
vec![]
is guarantied to not allocate memory, so there's no runtime overhead. In the general case you will need some "zero" value for your uncopiable type just like in C++. If you can't come up with "zero" value, you can always use Option
as CodesInChaos answer suggests.
Upvotes: 2
Reputation: 430791
No.
I know for sure that I didn't consume it
Just because you didn't write any code that didn't consume it doesn't mean that it wasn't consumed. Ownership and conditionally executed code discusses the mechanics of type- and stack-based drop flags further, but conceptually your code is:
let v = vec![1, 2, 3];
if false {
let _t = v;
drop(_t);
} else {
drop(v);
}
println!("{:?}", v);
Once the conditional is over, your value is as good as gone. (implementation-wise, the drops do happen at the end of the function, but the semantics don't express that).
in some condition
That condition would be the else
block of your if
statement:
if false {
let _t = v;
} else {
println!("{:?}", v);
}
Upvotes: 5