为什么存在下划线前缀变量?

45

我正在学习Rust,发现在变量名前加下划线会让编译器不警告未使用的变量。我想知道为什么会有这个功能,因为未使用的变量是被不赞成的。


2
Haskell 也是做同样的事情。也许这就是灵感来源。 - RichardForrester
5个回答

46
我可以看到几个原因:
  • 你正在调用一个返回#[must_use]类型的函数,但在你的特定情况下,你知道可以安全地忽略该值。可以使用_模式来实现这一点(它不是变量绑定,而是自己的模式,但这可能是下划线前缀约定的来源),但你可能想记录为什么要忽略该值,或者该值是什么。在我的经验中,这在测试中特别常见。
  • 函数参数:你可能需要命名一个参数,因为它是你的API的一部分,但实际上不需要使用它。匿名参数在2018版中已被删除。
  • 宏。在宏中创建的变量可能会被使用,也可能不会被使用。如果不能消除宏调用中的警告,那将很麻烦。在这种情况下,有一个加倍下划线的约定,例如clippy的used_underscore_binding lint就强制执行这个约定。
  • RAII。你可能希望一个变量存在于其析构器副作用中,但否则不使用它。对于这种用例,不能简单地使用_,因为_不是变量绑定,而且该值不会像变量绑定一样在封闭块的末尾被丢弃。

17
可以使用 _ 模式来实现这个。let _=let _foo= 有着稍微不同的行为。两者都可以消除警告,但是 let _ 会导致返回值在语句结束时被 Drop,而不是在作用域结束时被 Drop,因为没有绑定来持有该值。 - loganfsmyth
2
下一句话字面意思是“这不是一个变量绑定”。但我已经在“RAII”点中添加了这个备注,以便澄清。 - mcarton
1
好的。我认为大多数初学者不知道模式和绑定具有不同的丢弃行为,所以当您提到这一点时,我实在无法确定您是否是在说这个。 - loganfsmyth
1
@loganfsmyth 我猜那不是真的,可以看这个视频:https://youtu.be/b4mS5UPHh20?t=3054。尽管被赋值给下划线变量,但该值并未被丢弃。 - J. Doe
@J.Doe 我没看那个视频,但是loganfsmyth说得完全正确。 - mcarton
显示剩余3条评论

7
以下是一些例子,说明为什么您可能希望忽略未使用的变量。考虑以下函数中的 _s
fn add_numbers(f: i32, _s: i32) -> i32 {
    f + 1
}
_s变量使我们能够保持签名不变,即使我们没有实现它。如果我们发现不需要_s,但因为我们的库在许多不同的项目中使用,我们不想改变函数的API,这也适用。这可能是好的做法,也可能是坏的做法,但在_s需要保留且不执行任何操作的情况下可能会很有用。我们也可以在这里使用_,但_s未来可能具有更多的意义。
接下来,在类型实现Drop并且您关心该逻辑发生的位置时,这可以很有用。在此示例中,您可以看到需要_result变量,以便Drop在最后发生。
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");
        }
    }
}

如果我们使用let _result = try_add_numbers(&mut num);,那么我们就有了一个变量,它在main函数结束之前都处于范围内,直到drop被调用为止。如果我们使用let _ = try_add_numbers(&mut num);,我们仍然不会收到警告,但是drop会在语句的末尾被调用。如果我们没有let绑定而使用try_add_numbers(&mut num);,我们会收到警告。这个程序的输出结果取决于我们如何在try_add_numbers函数中使用它们。
It worked
2

或者

2
It worked

因此,在涉及到程序输出时,需要根据需要选择__named变量。请在Playground中尝试我的示例以加深理解。


2
我认为在Drop的情况下,手动调用drop比使用_result来隐藏实际上正在使用_result更有意义。在需要调用drop的情况下,使用的值仍然很明显,而且需要在某个地方删除它也很明显。 - Loïc Faure-Lacroix

7
  • let a是一个值绑定,会分配一个栈空间来存储它的值。
  • let _a表现得像let a,除此之外,它被标记为intentional,这样编译器就不会在_a未使用时弹出警告。
  • let _是一种模式,_是一个保留标识符,不能在其他地方使用。这不会导致栈空间的分配,所以=右侧的值将很快被释放。

以下是一个示例: 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();
}

输出结果为:

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

你可以阅读 这篇文章 了解更多。

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

-1

只需定义变量的类型,例如:

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

因此,通过您代码中的最新更改,您可以克服此错误:

  fn name(){ //here warning won't come
     let myname: &str = "bash"
       }
 

我不知道你回答时的情况是否有所不同,但目前添加类型注释并不能抑制未使用变量的警告:playground链接 - kmdreko
明白了,当我使用它时它是有效的。现在我不确定。 - Balanagu Yashwanth

-1

我通过谷歌搜索与匹配变量相关的警告时偶然发现了这里。这与其有关。

有时您可能会有一些代码,其中您获得一个Result并希望匹配情况,但您不关心错误值。您可以使用_来代替_e或其他东西,它明确地不绑定。以下是一个具体示例。我们不关心错误的值,因为我们正在返回自己的值。

fn some_method() -> Result<u32, MyCustomError> {
    // ...
    let id: u32 = match some_str.parse() {
        Ok(value) => value,
        Err(_) => return Err(MyCustomError::Blah)
    };
    // ...
}

问题是“为什么你会使用_e”。这意味着“实际上,你可以只使用_。”这怎么回答问题呢? - mcarton
@mcarton之前在学习Rust的玩具项目时写了这篇答案。由于我不记得自己是怎么到这里来的,所以也不记得我的确切逻辑。从上下文最好的猜测是,我做了类似于Err(e)的事情,并收到了一个关于它未被使用的错误。也许编译器甚至建议像use _e to avoid the warning这样的东西,然后我就来到了这里。我可能随后学会了可以使用_来避免错误。假设这就是OP正在处理的问题,那么它回答了潜在的关注点。无论如何,既然我因为_e来到了这里,我相信其他人也会受益于建议使用_ - Captain Man
@mcarton 我知道之前在 Meta 上已经讨论过这样的问题,但我记不起具体术语了。以下是与此问题有关(虽然不是直接相关)的帖子链接:https://meta.stackexchange.com/q/8891/289725 https://meta.stackexchange.com/q/66377/289725 - Captain Man

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接