Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does "|_|" mean in Rust?

Tags:

syntax

rust

I am learning how to connect database to Rust program. There something I don't quite understand:

PgConnection::establish(&database_url).unwrap_or_else(|_| panic!("Error connecting to {}", database_url))

What does this expression mean? What does |_| mean?

like image 314
izzy Avatar asked Sep 07 '25 03:09

izzy


1 Answers

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".

like image 195
Dai Avatar answered Sep 10 '25 09:09

Dai