- Feature Name:
explicit_closure - Start Date: 2024-01-13
Summary
Instead of generate inplace-types for each closure, generate an explicit type and data layout for each locally generated |x| { ... }.
Motivation
Explicit closure types allow passing a closure out of a function without wrapping the returned value inside Box<dyn ...> . Existing closure types become tricky when you try to return them from a function.
// without explicit closure type
fn curry_add(x: usize) -> Box<dyn Fn(usize) -> usize> {
Box::new(|y: usize| { x + y })
}
Introducing explicit closure type may solve this problem smoothly, while the old method is still applicable when two different data layouts are used for possibly returned closure type.
// with explicit closure type
fn curry_add(x: usize) -> impl Fn(usize) -> usize {
|y: usize| { x + y }
}
Of course, for explicit closure, all the captured data must be moved, and reference to stack-allocated variables should not be returned from a function.
Guide-level explanation
A closure is a function with attached ('captured') data. The captured data can stored an passed along with the function. For example, in the following function:
fn foo(x: X, y: Y) -> impl Fn(Z) -> W {
let t = x + y;
|z: Z| { z.mul(t).mul(x) }
}
The expression |z: Z| have type Closure<(X, T), (Z), W> , which means X and T are captured (and stored), (Z) is an argument, and W is the return type.
Reference-level explanation
For sequentially captured value X1, X2, ..., XN , argument types Y1, Y2, ..., YN and return type Z, the compiler automatically generates a closure type looks like: Closure<(X1, X2, ..., XN), (Y1, Y2, ..., YN), Z>, together with a function-typed value.
The closure type is declared as:
struct Closure<X, Y, Z> {
// captured data
data: X,
// points to an automatically generated function
primitive: fn (X, Y) -> Z
}
impl<X, (Y1, Y2, ..., YN), Z> Fn<(Y1, Y2, ..., YN)> for Closure<X, (Y1, Y2, ..., YN), Z> {
type Out = Z;
fn apply(&self, args: (Y1, Y2, ..., YN)) -> Z {
self.primitive(self.x, (y1, y2, ..., yn))
}
}