44

I am learning Rust, and came across the fact that adding an underscore at the beginning of a variable name will make the compiler not warn if it is unused. I am wondering why that feature exists, since unused variables are frowned upon.

Sir Platypus
  • 543
  • 1
  • 4
  • 9

5 Answers5

45

I can see several reasons:

  • You are calling a function that returns a #[must_use] type, but in your specific case, you know you can safely ignore the value. It is possible to use a _ pattern for that (which is not a variable binding, it's a pattern of its own, but this is probably where the underscore prefix convention comes from), but you might want to document why you ignore the value, or what that value is. This is particularly common in tests in my experience.
  • Function parameter: You might have to name a parameter because it's part of your API, but don't actually need to use it. Anonymous parameters were removed in the 2018 edition.
  • Macros. A variable created in a macro may or may not be used later. It would be annoying to not be able to silence warnings in a macro call. In this case there is a convention of doubling the underscores, this is enforced for example by clippy's used_underscore_binding lint.
  • RAII. You might want to have a variable exist for its destructor side effect, but not use it otherwise. It is not possible to use simply _ for this use-case, as _ is not a variable binding and the value would not be dropped at the end of the enclosing block as with variable binding.
Yuri Astrakhan
  • 8,808
  • 6
  • 63
  • 97
mcarton
  • 27,633
  • 5
  • 85
  • 95
  • 17
    *It is possible to use a _ pattern for that* `let _ = ` and `let _foo = ` have slightly different behavior. Both will stop the warning, but `let _` it will cause the returned value to be `Drop`ed at the end of the statement instead of at the end of the scope because there is no binding to hold it. – loganfsmyth Jan 20 '18 at 23:05
  • 2
    The next words were literally "which is not a variable binding". But I've added this remark in the "RAII" point, for clarification. – mcarton Jan 21 '18 at 00:25
  • 1
    Fair. I think most beginners would have no way to know that a pattern and a binding have different drop behaviors, so I honestly couldn't tell if that's what you were trying to say when you referenced that. – loganfsmyth Jan 21 '18 at 00:42
  • 1
    @loganfsmyth I guess that is not true, see: https://youtu.be/b4mS5UPHh20?t=3054. The value is not dropped although assigned to an underscore variable. – J. Doe Aug 13 '20 at 11:38
  • @J.Doe I didn't bother watching the video, but what loganfsmyth is absolutely correct. – mcarton Aug 13 '20 at 12:22
  • @mcarton What about this: https://codeandbitters.com/drop-or-not/? The value won't be dropped when assigned to a underscore variable. Or won't you bother reading the article? – J. Doe Aug 13 '20 at 12:27
  • Oh, but in the usual case of `let _ = foo();` the result _is_ dropped, in the special case of `let _ = another_binding;` then there is a difference, but no-one would ever write that. [The article](https://codeandbitters.com/drop-or-not/) summarizes that well with “ The wildcard pattern _ means "don't bind the result to a name", not "throw away the result". If the right side is a variable name, the "don't bind" behavior overrides the right side being evaluated as an expression (which would trigger a move).” – mcarton Aug 13 '20 at 14:04
  • Guys, check out godbolt if you're not sure. Using `let _` or not causes the same results if something was going to drop in place in the first place. And in other cases there's no assembly produced for it. In other words, it's a complete no-op (other than letting you ignore warnings). Dropping in place or not would've happened by itself or not even without that. The real way to drop is with `drop()`, assigning to another variable, or moving it (though technically, all those 3 are moves) – Kobato Oct 02 '22 at 21:34
7

Here are some examples as to why you might want the behavior of ignoring an unused variable. Consider _s in the following function.

fn add_numbers(f: i32, _s: i32) -> i32 {
    f + 1
}

The _s variable makes it so we can keep the signature the same even if we haven't implemented it. This also works if we found out we didn't need the _s but because our library is used in so many different projects we didn't want to change the API to our function. This may or may not be bad practice but could be useful in a situation where _s needs to stay and not do anything. We could also use _ here but _s potentially has more meaning as to what the variable is for in the future.

The next place where this can be useful is when a type implements Drop and you care where that logic happens. In this example you can see that the _result variable is needed so that the Drop happens at the end.

fn main() {
    let mut num = 1;
    // let _ = try_add_numbers(&mut num); // Drop is called here for _
    let _result = try_add_numbers(&mut num); // without the _result we have a warning.

    println!("{}", num);
    // Drop is called here for the _result
}

// keep the api the same even if an aurgument isn't needed anymore or
// has not been used yet.
fn add_numbers(f: i32, _s: i32) -> i32 {
    f + 1
}

// This function returns a result
fn try_add_numbers(i: &mut i32) -> Result<GoodResult, GoodResult> {
    if *i > 3 {
        return Err(GoodResult(false));
    }
    *i = add_numbers(*i, 0);
    Ok(GoodResult(true))
}

struct GoodResult(bool);

impl Drop for GoodResult {
    fn drop(&mut self) {
        let &mut GoodResult(result) = self;
        if result {
            println!("It worked");
        } else {
            println!("It failed");
        }
    }
}

If we use let _result = try_add_numbers(&mut num); we have a variable that is in scope until the end of main and drop will be called then. If we had used let _ = try_add_numbers(&mut num); we still don't get a warning but drop is called at the end of the statement. If we use try_add_numbers(&mut num); without a let binding we get a warning. The output of this program does change depending on which we use with our try_add_numbers function.

It worked
2

or

2
It worked

So there is a use for both _ and _named variables which need to be chosen based on what the output of your programs needs to be. Play around with my example on the playground to get a feel for it.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Daniel Wilkins
  • 447
  • 4
  • 7
  • 2
    I'd argue that in the case of the `Drop` it would make more sense to call `drop` manually than to use the `_result` to hide that `_result` is being actually used for something. In the case of calling `drop` where required, it would make the value used and still be obvious that it required to be dropped somewhere. – Loïc Faure-Lacroix Jun 11 '19 at 03:27
7
  • let a is a value binding, and a stack space will be allocated to store it's value.
  • let _a is something behaves like let a. In addition, it is marked as intentional, so that compiler will not pop up a warning if _a is not used.
  • let _ is a pattern, and _ is a reserved identifier which cannot be used elsewhere. This will not cause a stack space to be allocated, so the value on the right side of = will be released soon after this statement.

Here is a example: Playground

pub struct Node {
    value: usize,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("drop() {}", self.value);
    }
}

pub fn square() {
    let a = Node { value: 1 };
    let _a = Node { value: 2 };
    let _ = Node { value: 3 };

    println!("Hello, world!");
}

fn main() {
    square();
}

the output is:

drop() 3
Hello, world!
drop() 2
drop() 1

You can read this to learn more

imlk
  • 73
  • 1
  • 4
  • `let _` actually is a no-op. It does nothing. You can check the assembly yourself. `let _ = Node { value: 3 };` and `Node { value: 3 };` produce the exact same results. I created a small example to show this. You can try my example and this example above (both with and without `let _`, and you'll see the assembly is the same). https://godbolt.org/z/5vnr1ExWe - What `let _` is useful for, is for ignoring compiler warnings such as `warning: unused Result that must be used` – Kobato Oct 02 '22 at 21:19
-1

I stumbled here through Google while looking up this warning related to match variables. This is tangentially related.

Sometimes you may have code where you get a Result and want to match on the cases, but you don't care about the error value. Instead of using _e or something, you can actually just use _ which explicitly doesn't bind. Here's a concrete example. We don't care about the value of the error since we're returning our own.

fn some_method() -> Result<u32, MyCustomError> {
    // ...
    let id: u32 = match some_str.parse() {
        Ok(value) => value,
        Err(_) => return Err(MyCustomError::Blah)
    };
    // ...
}
mcarton
  • 27,633
  • 5
  • 85
  • 95
Captain Man
  • 6,997
  • 6
  • 48
  • 74
  • The question is “Why would you ever use `_e`”. This says “Actually, you can just use `_` instead.” How is this an answer to the question? – mcarton Aug 13 '20 at 14:28
  • @mcarton Wrote this answer a while ago now while learning Rust on a toy project. Can't remember my exact logic because I don't remember how I got here. Best guess from the context is that I was doing something like `Err(e)` and got an error about it being unused. Maybe the compiler even suggested something like `use _e to avoid the warning` and it led me here. I likely then learned that I could use `_` to avoid the error. Assuming that's what OP is dealing with it answers the underlying concern. Either way, since I got here due to `_e` I am sure others do too and suggesting `_` seems helpful. – Captain Man Aug 14 '20 at 18:24
  • @mcarton I know I've seen stuff like this addressed on Meta before but I can't remember the terms. Here are (again, tangentially) related posts about answers to "different questions" than the one asked: https://meta.stackexchange.com/q/8891/289725 https://meta.stackexchange.com/q/66377/289725 – Captain Man Aug 14 '20 at 18:29
-1

Just define type of the variable i.e

for example:-

  fn names(){ //this case the function gives an warning like "help: if this is intentional, prefix it with an underscore: `_myname`"
      let myname = "yash";  
      }

so by latest changes in your code, you can do to overcome this error:-

  fn name(){ //here warning won't come
     let myname: &str = "bash"
       }
 
  • I don't know if the situation was different when you wrote your answer, but as it currently stands adding a type annotation *does not* suppress the unused variable warning: [playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a6a18cd32b6711c4f52bb8bd15e3ac0b). – kmdreko May 27 '23 at 17:59
  • got it, when i used its working. now i am not sure – Balanagu Yashwanth May 29 '23 at 05:02