1

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?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
izzy
  • 53
  • 7
  • 2
    https://users.rust-lang.org/t/what-are-pipes-and-underscores-doing-in-rust/14601 – Dai Oct 01 '22 at 01:52
  • 2
    _"What does this expression mean?"_ - With apologies for pedantry, but in programming-languages the term "_expression_" [has a specific meaning](https://doc.rust-lang.org/reference/statements-and-expressions.html) - whereas if you want to point to some syntax or programming code construct you don't _grok_ you should say something like "What does this syntax mean?" or similar. – Dai Oct 01 '22 at 02:29
  • Related: [What does Rust's unary || (parallel pipe) mean?](/q/36988622/2189130) – kmdreko Oct 01 '22 at 02:47

1 Answers1

12

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

Dai
  • 141,631
  • 28
  • 261
  • 374
  • 5
    "Instead of being a conformist and using ( ) => like everyone else" besides the two languages you've mentioned, I can't think of any other programming language that uses `() =>` to denote lambdas. Haskell has `\ ->`, OCaml has `fun` or `function`, Python has `lambda :`, scheme has `(lambda () )`, Ruby has a similar pipe syntax, C++ has `[](){ }`, ... – jthulhu Oct 01 '22 at 07:14
  • Besides, closures in Rust have a single syntax, which is `|...| expr`. It's just that blocks `{ statement; statement; ...}` are also expressions, so if you want to have several statements, you have to use braces. – jthulhu Oct 01 '22 at 07:17
  • 2
    @BlackBeans Rust's pipe syntax is indeed taken from Ruby (https://doc.rust-lang.org/stable/reference/influences.html). – Chayim Friedman Oct 02 '22 at 00:06
  • @jthulhu I've rewritten the more controversial parts of my answer to be more accurate - and to cite Ruby. Thanks for the feedback. – Dai Feb 21 '23 at 18:35