How to hide variables in Rust that they are not accidentally used below

Hi,

is it possible to hide a variable in Rust, that it cannot be used anymore.
See this example. Something that my hide!-macro does.


#[cfg(test)]
mod tests {

    macro_rules! hide {
        ($var:ident) => {
            #[allow(unused_variables)]
            let $var = ();
        };
    }

    #[test]
    fn test_hide() {
        assert_eq!(calculate(10), 20);
    }
    
    fn calculate(amount: i32) -> i32 {
        // A) we can shadow it, but it has to be the same name
        let amount = amount + 10;
    
        // B) or we shadow it with `()`,
        // but we need make the compiler happy for `unused_variables`
        let mut sum = amount + 20;
        hide!(amount);
    
        // amount is useless now
        assert_eq!(amount, ());
    
        // this and that, but don't use amount anymore
        sum /= 2;
        sum
    }
}

I think that's very useful when you want to write auditable code. In my case I want to prevent that someone uses a variable by accident, that would fit by the type and the name, because it's used at the beginning of a function, but it shouldn't be used in the rest of the code. I can use expressive names and so on, but I think it would be neat to just be able to remove a variable from a scope or hide/shadow it with useless data. Do I need the hide! macro, or does Rust support it already somehow?

Thanks
Best regards
Philipp

If the type doesn't implement Copy, you can just drop(it). It will be destructed when dropped instead of when going out of scope.

You can use a nested scope.

You could strive to factor your code in such a way it's not an issue.

1 Like

Your macro looks pretty good, but personally, I'd put it in a block.

let mut sum = {
    let amount = amount + 10;
        
    amount + 20
};
3 Likes

So drop may not be possible, if you want to keep a value with same lifetime coming from the dropped variable (I have that use-case) and as you already mentioned, doesn't work for Copy, which may be interesting for calculations, to not use things twice. Maybe a good wrapper type, that intentionally doesn't implement Copy/Clone is safer to do no mistakes here.
Nested scope or code refactoring: that would be possible, but I think it makes it just more complicated at some point.

Ok, sure my code doesn't make sense at all, but the idea of "hiding" should become clear here. There is even a solution to make variables not getting dropped immediately, while they stay "hidden" let _ = mutex.lock().

Fair enough.

I did think of something like this: have a non-Copy type and do something like

let shadow = NonCopy;
drop(shadow);
// using shadow is a compile time error after this

(Sorry for the incomplete example; on mobile.)

Then you don't need a wrapper (which may change drop order) and Copy types are still handled.

2 Likes

A one-line way to create an unusable variable (given a suitable type declaration):

enum Stop {}

// ...

let x: Stop;

This variable is uninitialized, so it cannot be read or borrowed, and its type is uninhabited, so it cannot be assigned (unlike the drop()ped variable suggested above).

7 Likes

Nice. Possible name:

let x: BecomeUnusable;
2 Likes

If you define a macro between the point of defining the variable and hiding it using shadowing and make this macro return the variable, then using this macro after hiding the variable will still refer to the variable visible at the point the macro was defined, not the shadowing variable: Rust Playground

okay that's an improvement, that shadow cannot be used anymore, but your code generates an unused_variables warning, so again we need a macro.

That means, I have to create a macro each time I want to do it. That's not what I want :slight_smile:

I also like that solution without macro, but we need another type here, thank you.

Thought of another thing: if you make a macro for one of these, make sure you use the original variable before shadowing it so that if you mistype the variable name, you'll get an error.

1 Like

Reading this made me think of the case where you have references to the variable or reborrowed from the variable in question. Those will survive shadowing, but using the variable can eliminate some of them. However I don't think there's a silver bullet...

Name check &var borrow &mut var borrow Notes
let _ = var Survives Survives
let _ = &var Survives Eliminated
let _ = &mut var Eliminated Eliminated Error unless var was bound as mut
let _move = var Eliminated Eliminated Changes semantics (drop order)

...and in the limit, shared references held by a struct can be copied out of it,[1] allowing examination or (with interior mutability) mutation of data reachable from a given variable without the variable still being borrowed or otherwise "usable" itself.


  1. or maybe the variable itself is a shared reference ↩ī¸Ž

If you shadow an unused variable, you'll get an ununsed_variables warning as expected. If you have a typo and do not shadow a variable, then well :slight_smile: it's like doing nothing at all.

I tried a bunch of things to get both the unused warning and verify the variable exists, but I couldn't figure anything out. It's real easy to make other warnings instead of unused_variables, but they all remove the unused status on the original variable. Closest I got was making a feature and then compiling twice: one verifying the variable is used, and one verifying the variable exists. This is a bad solution; I'd rather pick one of the two than do this.