Ignoring the underscore _
for now, some exposition is necessary:
Closures and those pipe-characters |
:
The term "closure" is effectively synonymous with terms like "lambda-function", "anonymous function", "block", "inline function expression", etc - and it basically means a function-defintion with environment capture semantics, so the closure can use variables declared in a parent scope.
Closures, like any other function, are going to need a parameter-list, and Rust borrowed Ruby's use of pipes |
to denote closures' parameter lists - this is in contrast to other popular languages like JavaScript and C# that opted to use normal parentheses with a "fat arrow" to denote a closure, e.g. ( x, y, z ) =>
.
Rust's documentation has a section on their syntax for closures that I recommend people read.
By example, here is a quick comparison of semantically similar code that filters-out numbers greater-than-or-equal-to 5 from an input bigArray
shown in JavaScript, C#, Ruby, and Rust:
// JavaScript:
const smallerArray = bigArray.filter( ( num ) => num < 5 );
// C#:
Int32[] smallerArray = bigArray.Where( num => num < 5 ).ToArray();
Int32[] smallerArray = bigArray.Where( ( Int32 num ) => num < 5 ).ToArray(); // With parameter-list parentheses and explicit parameter types.
// Ruby:
smallerArray = bigArray.select {|num| num < 5}
// Rust:
let smaller_array = big_array.into_iter().filter( |num| num < 5 )
Rust, just like JavaScript, C# and Ruby also supports multi-statement closures - the 4 examples above are all single-parameter, single-expression closures, while equivalent multi-statement-bodied closures have slightly expanded syntax:
// JavaScript:
const smallerArray = bigArray.filter( ( num ) => { return num < 5; } );
// C#:
Int32[] smallerArray = bigArray.Where( ( Int32 num ) => { return num < 5; } ).ToArray(); // You can also use the *very-old* `delegate( Int32 num ) { ... }` syntax.
// Ruby:
smallerArray = bigArray.select do |num|
num < 5
end
// Rust:
let smaller_array = big_array.into_iter().filter( |num| { num < 5 } ); // Rust does not require a `return` statement here.
The underscore _
:
As for the underscore: Rust shares Python's and C#'s convention for using an underscore to denote a discarded or otherwise ignored parameter or local. Now you might wonder what the point of a discarded parameter is: why not just omit it entirely like in JavaScript? (e.g. bigArray.filter( x => x < 5 )
and bigArray.filter( ( x, idx ) => x < 5 )
and bigArray.filter( ( x, idx, arr ) => x < 5 )
are all equivalent.
...well, JavaScript doesn't support function overloading: each function name resolves to a single function implementation, so omitting unused parameters isn't a problem. Now while Rust doesn't support function overloading either, it's still a very large and complex language that has many situations where you will need to explicitly declare a parameter that you don't use (strictly speaking, _
represents an unbound identifier and you cannot use _
as a variable).
The main use-case for naming a function parameter to _
in Rust is because it's a value annotated with the "must_use
" attribute - but if you really know you don't need to use that (bound) parameter and you don't want to be bombarded with low-value compiler warnings about must_use
values, then _
is a handy solution.
Another use-cases of _
is to declare that a call-site's return-value is being willfully discarded (so this syntax signifies intent), for example:
let _ = someFunctionThatReturnsAValue();
...whereas if you simply put someFunctionThatReturnsAValue();
on its own line then anyone else reading the code, or a static-analysis tool, will think you absent-mindedly forgot to check someFunctionThatReturnsAValue
's return-value - but using let _ = ...
makes it clear that you really don't care about the return value such that you don't want static-analysis tools dinging you.
So given unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
, what does |_|
mean?
unwrap_or_else
is a method of std::result
.
unwrap_or_else
's parameter is a callback function op: FnOnce
. If the result
is-not-Ok
then op
will be invoked and op
's return-value becomes the end-result of the unwrap_or_else
expression (or it panics... your call).
- Crucially, the
op
function in unwrap_or_else
accepts one (1) parameter which is the E
error-value contained within the result
.
- In this case, the
panic!("Error connecting to {}", database_url))
expression doesn't use the inner error value at all (which I think is a bad idea) so the callback closure/function discards the value by using the _
syntax.
So in conclusion, |_|
in unwrap_or_else(|_| panic!("Error")
means "The argument of unwrap_or_else
is an anonymous function accepting an E
(error value)-typed parameter - buuuut we just don't care about it, so pretend it doesn't exist".