Why do we need _?

What is the reason of using _ ?

(_ , ... , ...)
_ = ...() ;

instead of:

(, ... , ...)
...() ;

Are there situations, where _ helps ?

It's unclear what your question is. _ is a placeholder for any type (in type declarations) or any (sub)pattern (in patterns). It's useful for instructing the compiler to infer a type or ignore a pattern, without having to specify all types and all subpatterns explicitly.

5 Likes

Thank you for response. I am talking: Instead of using:

_ = fn1();

in would be nice to use more elegant way:

fn1();

Calling fn1() alone will trigger #[must_use] warnings, if the function or its return type are annotated such. Assigning to _ is one way to suppress that.

3 Likes

This means "explicitly ignore this".

fn1 returns some type annotated with #[must_use] like Iterator implemented types, or Results or Options, etc. If I write fn1() then the compiler infers that I've forgotten to deal with those values.

If I instead write let _ = fn1() then I mean explicitly ignore this.

4 Likes

You definitely can call a function without assigning its return value to anything. This is already possible, in today's Rust.

1 Like

This alternative would be ambiguous in patterns like Some(_) vs Some().

2 Likes

_ is a special pattern in that it doesn't actually bind anything.[1] This effects the drop scope of things being matched on the right hand side.

// Doesn't bind the RHS to anything.  Return value immediately drops.
// Doesn't count as a use for the sake of `#[must_use]`.
f();

// Binds the RHS to `variable`.  Drop scope is the end of the block.
// Counts as a use.
let variable = f();

// Binds the RHS to `_variable`.  Drop scope is the end of the block.
// Counts as a use.  No warning if `_variable` itself is otherwise unused.
let _variable = f();

// *Doesn't bind the RHS to anything.*  Return value immediately drops.
// Counts as a use.
let _ = f();

This can also be relevant in match statements, where your _ pattern might be buried somewhere deeper.

It can be relevant when your drop location matters, like when locking a Mutex.


  1. Option 2 here. ↩︎

3 Likes

This might give the false impression that

let _ = EXPRESSION;

and

EXPRESSION;

are always the same, besides the difference in #[must_use] warnings.

That’s not the case.

EXPRESSION;

behaves more like

drop(EXPRESSION);

The difference becomes apparent when EXPRESSION is a place expression. (For value expressions like f() there really is no difference though :slight_smile: )

let x = String::from("hello");

x; // drops x
// compilation error:

let x = String::from("hello");

x; // drops x
x; // cannot work twice
// compiles fine:

let x = String::from("hello");

let _ = x; // does nothing

// so let's do nothing again…
let _ = x; // does nothing
let _ = x; // does nothing
let _ = x; // does nothing

By the way… these expressions are a bit weird…, they quite literally act as if they weren’t there:

// compiles fine:

let mut x = String::from("hello");
let r = &mut x;
let _ = x; // yes, *really* does nothing
*r += " world";
println!("{x}");
let _ = r; // yes, *really* does nothing
drop(x);
let _ = x; // yes, *really* does nothing
8 Likes