{{post#0}}

Rust doesn't have any explicit way to give closures a more recognizable name. Closures end up having non-descriptive names {{closure#0}} in paths and symbols, and noisy-looking types {closure@span}.

When I'm profiling code or checking cargo bloat output, having an anonymous …::{{closure}} symbol show up is annoying, because it can be difficult to figure out which closure is it.

JavaScript has a neat trick for this:

let frobnicate = () => {};
console.log(frobnicate.name);
> frobnicate

let tmp = frobnicate;
console.log(tmp);
> function frobnicate()

When closures are defined inside a let/var/const statement, they are named after the binding's name. rustc could do the same!

26 Likes

I like the idea, but this would unfortunately cause a lot of closure signature inference failures. An attribute on the closure would not have this problem (but would be a lot more syntactic noise).

My interpretation was that @kornel is only suggesting this in a diagnostic capacity (and maybe in debug symbols as well for backtrace purposes etc).

9 Likes

Closure inference heavily relies on function parameter bounds, and thus things often break when you assign a closure to a variable and then pass it, instead of defining the closure in place. That limits the usefulness of the proposed feature.

It would be nice to fix that issue somehow (for many reasons beyond just this feature).

7 Likes

You could name the closure something that indicates "second closure in fn foo passed to a map() call" etc as well. Wouldn't just have to be assignment to variables that tried to give helpful names.

Or maybe take the first part of the body of the closure as part of the name? Or the line number?

They already do.

  = note: expected unit type `()`
               found closure `{closure@src/main.rs:2:13: 2:15}`

Maybe like

foo(/** name */ |bar| {
  // ...
});

?

1 Like

Maybe Rust's logic could be different, e.g. name it after the method taking the closure:

iter.map(|| /* {{closure:map}} */)
  .filter(|| /* {{closure:filter}} */)

and/or inherit the binding name even if it's not directly applied (Firefox does this in JS too):

let widgets = iter.map(|| /* {{closure:widgets#0}} */)
  .filter(|| /* {{closure:widgets#1}} */)
16 Likes

I would personally like it if closures were named after the binding they belong to (like let x = || {}; being {{closure:x}}), and in free-standing closures that are passed as arguments then the natural name would be the name from the argument itself, but for Iterator::map the argument name is an uninspired f :sweat_smile:

5 Likes

I thought of that too, but it's not really a blocker; it will probably just lead to more descriptive argument names at the declaration site.

-    fn map<B, F>(self, f: F) -> Map<Self, F>
+    fn map<B, F>(self, mapper: F) -> Map<Self, F>
     where
         Self: Sized,
         F: FnMut(Self::Item) -> B,

(Assuming the names are coming from the trait declaration and not the effective implementation anyway. Not sure how I feel about the relative merits of that choice.)

5 Likes

Yeah I think using the argument name makes sense. We can always change the name used in the stdlib of that would help people.

3 Likes

There’s also the option of combining the function and argument name; imagine getting {{closure:map/f}}. That feels nice so you don’t have to pick names just for the occasional visibility of panic/debug output. Not sure how hard that would be to implement though.

16 Likes

While I was looking for something else, I noticed that -Zspan_free_formats=yes exists already, which renders like this:

error[E0308]: mismatched types
  --> $DIR/issue-20862.rs:2:5
   |
LL | fn foo(x: i32) {
   |               - help: a return type might be missing here: `-> _`
LL |     |y| x + y
   |     ^^^^^^^^^ expected `()`, found closure
   |
   = note: expected unit type `()`
                found closure `{closure@foo::{closure#0}}`

instead of

error[E0308]: mismatched types
  --> $DIR/issue-20862.rs:2:5
   |
LL | fn foo(x: i32) {
   |               - help: a return type might be missing here: `-> _`
LL |     |y| x + y
   |     ^^^^^^^^^ expected `()`, found closure
   |
   = note: expected unit type `()`
                found closure `{closure@$DIR/issue-20862.rs:2:5: 2:8}`

Making the output proposed in this thread for closures from the local crate would be quite easy. Funnily, for example wouldn't be able to use any of the proposals as the closure isn't bound to anything, other than the return value (so it could be something like {closure@foo::return}).

1 Like

I'm not in love with the results necessarily, but I feel it is a step in the right direction: Tweak closure string when using `-Zspan_free_formats` by estebank · Pull Request #144337 · rust-lang/rust · GitHub

Seeing the changes in context, I think that instead of something like {closure@main::{_::filter#0}}, the output should be along the lines of main::{closure _::filter#0}.

4 Likes

Interesting, what is the effect on binary/debug info size? The new names are longer, but how often they are repeated / how common these sort of symbols are will determine if it a minor change or substantial.

The symbols stored in the binary don't change, those use their own pprinter (actually two) that reside in rustc_symbol_mangling.

1 Like

At least it’s probably not strictly necessary to repeat three times that it’s indeed a closure (:

Isn't that a nested closure? So there is just one repeat ("found closure")? At least that is how I read it.

I tend to agree. We don't specify the type description in every message like we do in the "expected/found" note, so you'll need a bit of redundancy anyways. I think something like

found closure `foo::{closure _::map#0}`

which composes somewhat fine when you have nested closures

found closure `foo::{closure x}::{closure _::map#1}::{closure y}`

even if it is a bit more verbose than

found closure `{closure@foo::{x}::{_::map#1}::{y}}`
1 Like

No, that message (with the current formatting) has

found closure `{closure@foo::{closure#0}}`
      -------  ---------     ------------
      |        ||            |          |
      |        ||            |          closing brace for closure path
      |        ||            closure name
      |        |prefix for a closure type
      |        opening brace for closure path
      type description

I think that we can remove the prefix and maybe even the opening and closing braces (like, funnily enough, we already do for the symbol names).