The verbose closure example in the official Rust book

When I started learning Rust one year ago, I had some trouble understanding the benefit of the first large example in Closures: Anonymous Functions that Capture Their Environment - The Rust Programming Language. I read it twice, and then continued without seeing the real point of that example. Well, I think I understand at least the basics of Rust closures now. But I just returned to exactly that example, to check if I might now be able to see its purpose.

But I still can not really see it. The reason why in

fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
        user_preference.unwrap_or_else(|| self.most_stocked())
    }

a closure is passed as a parameter is only, because unwrap_or_else() is defined in exactly this way, i.e. expecting a closure. If it was defined expecting a value instead, we might just have a plain function call like "unwrap_or_else(self.most_stocked())"".

most_stocked() is a method with a self parameter, so we can call it like

fn main() {
    let store = Inventory {
        shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
    };
    
    println!("Most stocked: {:?}", store.most_stocked());

Of course I do understand the use and benefit of Rust closures, and of course it makes sense that unwrap_or_else() expects a closure -- e.g. to evaluate a larger expression.

But in my opinion, this example helps not to motivate and understand Rust closures.

unwrap_or_else is designed to call the closure only if necessary. If user_preference is Some, then it doesn't call the closure, so most_stocked() doesn't run.

An alternative user_preference.unwrap_or(self.most_stocked()) would work, but it would always call self.most_stocked(), regardless whether it's needed or not. If most_stocked() was an expensive function, it would be wasteful to call it when it's not needed.

// unwrap_or_else with a closure
match user_preference {
    Some(pref) => pref,
    None => self.most_stocked()
}
// unwrap_or without a closure
let default = self.most_stocked();
match user_preference {
    Some(pref) => pref,
    None => default,
}
1 Like

I do understand that.

What I do not understand is what people can really learn from this verbose first example. At least I had the feeling that I learned not much and that it only confused me.

Why the || syntax is required — that's just a design choice. Rust likes the code to be locally explicit, and behave consistently. When you see foo(bar()) you know that bar() is called and runs immediately, and you don't need to know how foo() is defined. If Rust inserted closures magically, then foo(bar()) could either mean foo(|| bar()) or let tmp = bar(); foo(tmp) depending on the function declaration that you don't see, so when you read the code you wouldn't be sure what function runs when.

Another aspect of that is generic code and type inference. Functions can be declared with abstract types, where such magic can become ambiguous. You can theoretically have Option<impl Fn()> that holds a closure. It's possible for a function to return another function/closure, so then it wouldn't be clear if the magic closure-wrapping is supposed to happen or not. Type inference tries to resolve such ambiguities from the context, but the more flexibility there is, the more ambiguity there is to resolve. When there's an error in the code, types that are too flexible and too abstract tend to create big confusing errors.

1 Like

Thanks for you fast and detailed replies. Perhaps my question should have been: Does that concrete example really shows that here a closure is needed and their benefits would become evident for a reader who knows nothing about closures? But OK, readers can always skip that section, in the same way as I did a year ago.

From educational point of view, I suppose that capturing self is not obvious. The method needs to be called on a specific object, and unwrap_or_else(Self::most_stocked) would lose that association, and unwrap_or_else(self.most_stocked) is not a valid syntax for method references.

But apart from that, unwrap_or_else(|| …) is a useful, commonly used pattern in Rust.

1 Like