1. 10
  1. 1

    It’s a great read for me, but I don’t quite understand the following part:

    Since each closure has its own type, there’s no compulsory need for heap allocation when using closures: as demonstrated above, the captures can just be placed directly into the struct value.

    How does each closure having a distinct type eliminate the need for heap allocation? While i32 and Vec have different types, they both normally live on the stack (unless you explicitly use something like Box).

    1. 3

      He’s saying that since each has its own distinct type, there’s no inherent need to abstract them behind a trait object. They can be stored directly on the stack instead of being behind a trait object pointer (Box<dyn Trait> or &dyn Trait)

      1. 1

        Suppose |x| x + 1 and |x| x + 2 have the same type, then I can do something like vec![|x| x + 1, |x| x + 2], which seems to have the same effect of using trait objects?

        1. 2

          Those two closures do not have the same type. Here’s a Rust Playground example showing this. Each closure is compiled into its own struct which implements the correct traits (from Fn, FnMut, and FnOnce) and which (if they don’t capture from the environment) may be coerced to a function pointer. Their types, even if the closures are identical, are not the same.

          1. 1

            Exactly, and that’s why I said suppose they have the same type :P

            You pointed out that each closure having a unique type lifts the restriction that closures must be put behind a trait object, and I was saying there is no such restriction even if all closures with the same signature share one common type, because in that case we can do stuff like vec![|x| x+1, |x| x+2] which normally calls for trait objects.

            1. 2

              From a type system pov you’d have two different implementations of Fn/FnMut for the same type, since you want different function bodies each. That would be kind of weird. If you then put two instances into the same vec I’m not entirely sure how Rust would find the correct trait impl without adding extra markers on the struct. Which smells like dynamic dispatch already.

              1. 1

                I suppose the compiler can look at the closures and figure out the least restrictive trait for each closure (can I make this Fn, No? then what about FnMut?Also no? Well I guess it’s a FnOnce then) and find the greatest common divisor of all closures in the Vec.

                1. 2

                  That’s not the issue. Even if there was only one trait for all, you would have overlapping implementations of the trait. The traits have a call method that will need to be implemented differently, one for x+1 and one for x+2.

                  1. 2

                    Oh, I see. In the case where some closures share a common type, instances of that type may include an additional field holding the pointer to its function body, which is essentially dynamic dispatch. That doesn’t call for heap allocation though, because the function pointers typically refer to .TEXT instead of the heap.

                    EDIT: I realized that if you put the instruction for a closure on the stack, then it would not have 'static lifetime and thus cannot be returned from a function. See more discussion here.

    Stories with similar links:

    1. Finding Closure in Rust (2015) via nalzok 7 months ago | 6 points | 1 comment