Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move Copy values #3670

Open
crlf0710 opened this issue Jul 9, 2024 · 5 comments
Open

move Copy values #3670

crlf0710 opened this issue Jul 9, 2024 · 5 comments

Comments

@crlf0710
Copy link
Member

crlf0710 commented Jul 9, 2024

Copy values have special copy semantics, when they're moved away, the old place still have valid value alive. A lot of times this is not people want. For example, it's very easy to typo and wrongly use some temporary values after they have been sent away with assignment.

If possible, i'd propose a move expression:

`move` `(` expr `)`

The parenthesis are for avoiding the ambiguity with move closures.

Example:

let a = 42;
x.a = move(a);
double(&mut x.a);
dbg!(a);  // compile error

Alternatives:

  • unlet a;
  • a.move
  • let a = 42 in { ... };
@Diggsey
Copy link
Contributor

Diggsey commented Jul 9, 2024

For example, it's very easy to typo and wrongly use some temporary values after they have been sent away with assignment.

I've never seen this be a problem, since by definition the old value is still valid? Do you have an example of where this can cause a problem?

Assuming there's an example where this is a problem, why would someone remember to use the "move" modifier, if they forgot not to use the moved value? Typically these problems are solved in the type definition rather than the use-site, since it avoids this problem, but this solution already exists - you can just not implement Copy for types which you want to be moved.

@kennytm
Copy link
Member

kennytm commented Jul 9, 2024

macro_rules! unlet {
    ($a:ident) => {
        #[allow(unused_variables)]
        let $a: (/* this variable has been disabled in this scope */);
    }
}
let a = 42;
x.a = a;
unlet!(a); // <-- new
double(&mut x.a);
dbg!(a);  // error[E0381]: used binding `a` isn't initialized

or you could just use { … } to impose the normal scoping rule...

{
    let a = 42;
    x.a = a;
} // <-- restrict scope of `a`
double(&mut x.a);
dbg!(a); // error[E0425]: cannot find value `a` in this scope

@crlf0710
Copy link
Member Author

crlf0710 commented Jul 9, 2024

Yes, this unlet macro is good enough.

I'm still a little curious why @Diggsey has never seen this be a problem. When writing algorithms, and there's more than one pointers or indices or something lightweight but copyable. I feel this is so common. Sorry I don't have code to share at the moment though. These types cannot be defined as non-copyable but this invalidation only happens within specific portion of algorithm.

@kennytm
Copy link
Member

kennytm commented Jul 10, 2024

These types cannot be defined as non-copyable but this invalidation only happens within specific portion of algorithm.

Sorry but this speaks more about the API design of the code than the Rust language. It is trivial to wrap the Copy type in a non-Copy structure such as

#[repr(transparent)]
#[derive(Debug)]
struct NoCopy<T>(T);
impl<T> NoCopy<T> {
    pub fn new(inner: T) -> Self {
        Self(inner)
    }   
    pub fn into_inner(self) -> T {
        self.0
    }
}
    let a = NoCopy::new(42); // <-- wrap the type into NoCopy
    x.a = a.into_inner(); // <-- consume the NoCopy
    double(&mut x.a);
    dbg!(a);  // error[E0382]: use of moved value: `a`

You could further enhance the safety by making the API accept specialized NoCopy-styled wrapper types only, and also making their new() and into_inner() private to the low-level modules.

And even if this RFC is implemented, as @Diggsey said in the 2nd paragraph of their comment, if you forget to annotate a move(a) the compiler will never be able to notice because copying is still valid, so this RFC offered no safety guard against misuse. Meanwhile if the variable a itself is declared NoCopy<i32> instead (i.e. "solved in the type definition rather than the use-site"), Rust can easily statically prevent double-use.

@clarfonthey
Copy link
Contributor

Note that there's also this RFC which attempts to solve a similar no-move problem: #3518

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants