How do I make a `Copy` value inaccessible after use?

94 Views Asked by At
struct S {
    foo: i32,
    // ... other fields
}


fn f(s: S) {
    // transform foo
    let new_foo = biz_logic(s.foo);
    // from now on, the code should read `new_foo` whenever it needs `s.foo`,
    // how do I "drop" the `s.foo` to prevent misuse?
    // obviously `drop(s.foo)` won't work since it is `Copy`
    // is there any idiom to do this?
    drop(s.foo);

    // OK
    let _ = ... new_foo ...;
    
    // Not OK
    let _ = ... s.foo ...;
}

Is there less known rust feature that let's you achieve the same result as drop() on a Copy?

3

There are 3 best solutions below

1
Filipe Rodrigues On

One option is to use variable shadowing. However, you can't shadow s.foo, so unless you're willing to replace its value with new_foo and keep using s.foo, you can bind s.foo to a local variable, foo, then shadow it with the result of biz_logic:

fn f(s: S) {
    let foo = s.foo;

    // Use `foo` for whatever
    // ...

    // Shadow `foo` so the old value can't be accessed anymore.
    let foo = biz_logic(foo);

    // Usages of `foo` will always access the new `foo`.
}
1
Sam On

If you're not interested in the rest of s, then destructuring and dropping will work:

fn f(s: S) {
    let S { foo, .. } = s;
    drop(s);

    // okay
    println!("{foo}");
    
    // not okay
    // println!("{}", s.foo);
}

Unfortunately, if you want to get at the rest of s (assuming some of it isn't Copy) this approach won't work, since the destructuring assignment will result in a partial move that will leave s.foo accessible and s unable to be manually dropped.

A better bet is probably destructuring in the function signature and shadowing:

fn f(S { foo, .. }: S) {
    let foo = biz_logic(foo);

    // foo is now result of biz_logic and foo from S is unreachable
}
0
Silvio Mayolo On

If you want to change the properties of an existing type, you're looking for the newtype pattern. We can make a newtype wrapper of i32 that behaves like i32 but without the Copy implementation.

struct MyI32Resource(pub i32);

Then the type of s.foo can be MyI32Resource, which obeys Rust's default move semantics since we haven't implemented Copy for it. You can implement whatever i32-like traits you need on MyI32Resource to delegate to the inner field. Likewise, you can impl Deref to be able to deref your new resource type into an i32.

This approach should only be used for situations where it's semantically inappropriate to use the existing value after moving. That is, where you're truly thinking of the i32 as a resource of some kind that truly can't be copied (Unix-style file descriptors are a great example of this; they're just integers, but semantically we want to impose resource-like constraints on them).

If you're just worried about someone reusing the same name in a particular function, then this approach is likely overkill, and Filipe's approach is cleaner for that.