kaist-cp / cs220

268 stars 53 forks source link

[Assignment ?][11/13 Lecture Question] "Is it possible to define a generic closure in Rust?" #424

Open Jeong-jin-Han opened 1 week ago

Jeong-jin-Han commented 1 week ago

Related Issue

https://github.com/nrc/r4cppp/blob/master/closures.md

Googling Result

https://users.rust-lang.org/t/use-generics-in-closure/86679

ChatGPT Result

https://chatgpt.com/c/

Your question here

I tried defining a generic closure in Rust like this:

let example_closure<T> = |x: T| x;

However, this produces a compiler error: expected one of ':' , ';', '=', '@', or '|'. I understand that closures in Rust infer types based on their first use, but is there a way to explicitly make a closure generic without wrapping it in a function or struct? If not, could you explain why this limitation exists?

Lee-Janggun commented 1 week ago

There isn't. I'm not sure why it isn't supported, but my guess is that closures are typically single-use functions, so there is no real point on making it generic in the most case. Also, generic code needs to be monomorphized at compile time, and it would be very difficult to account to do that for arbitrary let statements.

Jeong-jin-Han commented 1 week ago

Thank you!!

Jaewookim08 commented 3 days ago

I came across an RFC about adding generic closures, but it seems like it's been postponed forever—just like so many other cool features proposed for Rust...

https://github.com/rust-lang/rfcs/pull/1650#issuecomment-276528792:

I suggest we postpone this RFC until we're a good bit further along with the impending trait system revamp, which I think should make this kind of thing much easier to implement and reason about.

Nonetheless, the syntax you've tried for (let example_closure<T> = |x: T| x;) would never work. Below is a simple description on how closures are implemented in Rust and why the syntax won't work, so please have a look if you're interested.


Closures are structs.

Unlike normal Rust functions, which we reference by primitive 'function pointer' types (fn), each closure has its own type, auto-generated by the compiler. For example, the following code:

let b = 3u32;
let a = |x: u32| x + b;

is transformed by the compiler to the following:

struct __some_anonymous_type {
    b : u32,  // all captured values become a member of the closure struct.
}
impl Fn<u32> for __some_anonymous_type { // compiler auto-generates `Fn` implementation.
    fn call(&self, x: u32) -> Self::Output {
        x + self.b
    }
}
// `FnMut` and `FnOnce` are also implemented similarly.

let b = 3u32;
let a = __some_anonymous_type{ b };

Then we can 'call' this value 'a' of an anonymous struct type, just as we call normal functions.

assert_eq!(a(7), 10);

This 'call' on a struct is allowed since a function call (f(arg0, arg1, ...)) uses one of the traits: Fn, FnMut, or FnOnce. The Rust compiler determines which trait to use for each function call based on context. For more information about the differences between these traits, refer to the Rust book.

Now it is clear why your generic closure code doesn't work.

let example_closure<T> = |x: T| x;

would be interpreted as:

let example_closure<T> = __some_closure<T>{};

This would require generic variables, which don't exist in Rust for obvious reasons.

Generic closures in other languages

C++'s template lambda

C++ supports lambdas (closures) to use templates, a concept similar to generics in Rust.

auto a = []<typename T>(T x) { return x; };

Similar to Rust, the compiler will translate this into a struct instantiation.

struct __anonymous{
    template<T> T operator ()(T x) const { return x; }
}
auto a = __anonymous{};

Note that C++ doesn't require the template type T to have trait bounds, as C++ uses compile-time duck typing, unlike Rust.

If it walks like a duck and it quacks like a duck, then it must be a duck.

Dependent types

A few programming languages such as Coq or Idris utilize first-class types, meaning that types can be given to and returned from functions just like other values. This concept is also called dependent types—a type can depend on a value. Using this, we can mimic generic functions.

Definition a (T : Type) (x : T) := x.

Note that the return type of a, T, depends on the first argument value T: Type. (x is a value of type T, and T is a value of type Type. I know that sounds weird...)