Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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.

like image 322
Evan Avatar asked Jan 07 '18 21:01

Evan


People also ask

What are moves and copies in rust?

Moves and copies are fundamental concepts in Rust. These might be completely new to programmers coming from garbage collected languages like Ruby, Python or C#. While these terms do exist in C++, their meaning in Rust is subtly different.

Where can I find more information about the move keyword in rust?

For more information on the move keyword, see the closures section of the Rust book or the threads section.

How to make a deep copy of a value in rust?

When a value is moved, Rust does a shallow copy; but what if you want to create a deep copy like in C++? To allow that, a type must first implement the Clone trait. Then to make a deep copy, client code should call the clone method: This results in the following memory layout after the clone call:

How do I use console commands in rust?

Simply hit the escape key and open the options tab! While it's now easier to edit many things, console commands still come in handy. Below is a list of use Rust console commands for both players and admins.


2 Answers

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

like image 93
CodesInChaos Avatar answered Nov 15 '22 09:11

CodesInChaos


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);
}
like image 44
Shepmaster Avatar answered Nov 15 '22 09:11

Shepmaster