Evan
Evan

Reputation: 109

Is there a way to force Rust to let me use a possibly moved value?

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

Answers (3)

CodesInChaos
CodesInChaos

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);
            }
        }
    }
    

    playground

Upvotes: 11

red75prime
red75prime

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

Shepmaster
Shepmaster

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

Related Questions