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

Why do underscore prefixed variables exist?

我在学习Rust,遇到在变量名开头加下划线,如果没有使用,编译器不会提示。我想知道为什么存在该功能,因为不使用未使用的变量是不受欢迎的。

我可以看出几个原因:

  • 您正在调用一个 returns 类型为 #[must_use] 的函数,但在您的特定情况下,您知道可以安全地忽略该值。可以为此使用 _ 模式(这不是变量绑定,它是自己的模式,但这可能是下划线前缀约定的来源),但您可能想要记录为什么忽略该值,或者该值是什么。根据我的经验,这在测试中尤为常见。
  • 函数参数:您可能必须命名一个参数,因为它是您 API 的一部分,但实际上并不需要使用它。 Anonymous parameters were removed in the 2018 edition.
  • 宏。在宏中创建的变量以后可能会或可能不会被使用。无法在宏调用中消除警告会很烦人。在这种情况下,有一个将下划线加倍的约定,例如由 clippy 的 used_underscore_binding lint.
  • 强制执行
  • RAII。您可能希望为它的析构函数副作用而存在一个变量,但不要以其他方式使用它。对于这个用例,不可能简单地使用 _,因为 _ 不是变量绑定,并且不会像变量绑定那样在封闭块的末尾删除值。

以下是一些示例,说明您可能希望忽略未使用变量的行为的原因。考虑以下函数中的 _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 上的例子来感受一下。

我在查找与匹配变量相关的警告时偶然发现了这里 Google。这是切线相关的。

有时您可能会在代码中得到 Result 并希望匹配大小写,但您并不关心错误值。除了使用 _e 之类的东西,你实际上可以只使用 _ ,它明确地不绑定。这是一个具体的例子。我们不关心错误的值,因为我们返回的是我们自己的值。

fn some_method() -> Result<u32, MyCustomError> {
    // ...
    let id: u32 = match some_str.parse() {
        Ok(value) => value,
        Err(_) => return Err(MyCustomError::Blah)
    };
    // ...
}
  • let a是一个值绑定,会分配一个堆栈space来存储它的值。
  • let _a 的行为类似于 let a。另外标记为intentional,这样编译器在没有使用_a时不会弹出警告。
  • let _是一个模式,_是一个reserved identifier,不能在别处使用。这样不会导致分配一个栈space,所以=右边的值会在这条语句后很快被释放

这里有一个例子: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

您可以阅读 this 以了解更多信息