This RFC allows #[derive] attributes to be used on closures and the experimental generators.
Motivation
The primary motivation comes from a case where you want to serialize a generator state machine; a manually coded state machine can be used with derive but the compiler generated one is not.
This also acts as an alternative to the recently stabilized automatic Copy/Clone on closures.
Explanation
You can do arbitrary derives on closures and generators:
let closure = #[derive(Clone, Serialize)] || {};
When passed to a custom derive, we will make two minimal guarantees:
A closure is a struct, and a generator is an enumeration.
There’s no hidden external states. Which means, the compiler does not add any global state or interior mutability on its own.
Unresolved questions
This conflicts with what we have done with copy/clone_closures. Maybe introduce a deprecation list and change the behaviour with editions?
This needs some syntax discussion if we merged async fn which syntactically looks like a fn while returning a generator.
Can we provide stronger guarantee for the fields, for situations like migrating serialized data?
In this case, it seems like the closure will already need to satisfy Copy and/or Clone, which makes derive seems odd to me. Using impl would explicitly state the expectation that the closure already meets those requirements.
The initial post describes this as “an alternative to the recently stabilized automatic Copy/Clone on closures.” I don’t think this makes sense- it’s already stable, the change isn’t worth an edition, and the current behavior matches other literals.
But presumably in other cases derive would do what it always does- create a new impl for the named trait. The initial post doesn’t go into any detail on exactly how this would work—anonymous types are not something we can just convert to a TokenStream to hand to custom derive, for example—but it seems that was the intention.
I don’t see how the macro for Serialize could possibly work without getting type information about the closure. And as far as I know there are no plans to support such macros.
There are serious implementation blockers to supporting something like this. The actual structure of the anonymous type for both closures and generators is not generated until well into MIR, whereas derives are run during parsing. It would be a major refactor, maybe impossible, to generate this information prior to running the derives, because the information depends on type information, and typecking in general could depend on the code generated by the derive.
The point is, if we can derive code from macros to add additional traits to closure types, should we also be able to add those traits explicitly?
If we could, this will then give us an obvious way to implement those macros, because all the macros have to do is to convert it to an explicit trait.
But this is boiled down to my previous idea: something similar to Java’s anonymous class.
So the following
let closure = #[derive(Clone, Serialize)] || {};
can be converted to
let closure = struct {
/* explicitly captured fields */
/*Note: anynymous types can only be structs, not enums*/
/*Because this part should always be private and so the variant name will not be accessable*/
/* this part can de derived automatically in human written code */
} :
impl Clone {
fn clone(&self) { /*generated by macro*/ }
} +
impl Serialize {
/* generated... */
} +
impl FnOnce() {
type Output = ();
//ignore "rust-call"
fn call_once() {
/* closure code */
}
};
Yes, this gives another way to add traits to anonymous. But it will requires anonymous object to always be callable closures. In my variant, you can have anonymous types that does not implement FnXXX traits.
I question whether this is really useful at all (ignoring the high implementation cost, for discussion’s sake)? Let’s look at the vanilla derivable types: Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, and Default.
Copy and Clone are super useful – so useful that we’ve already special-cased them in the compiler.
Debug is vaguely useful, for introspecting the captures? I’d like to see a compelling scenario in which which debug-printing a closure’s captures outside of the closure’s body is useful.
PartialEq and Eq are only useful if you’re comparing copies of the same closure by their captures. The least pathological example I can think of is if you had some kind of function like
and tried to compare that they return the same integer… but even this seems pretty silly.
PartialOrd and Ord suffer from the same problem – plus, what order are the captures laid out in? This is currently unspecified, which means that the order is some non-canonical, unspecified ordering, which I am skeptical about.
Hash would primarially be useful for using closures as keys into a hash table… at which point you should be using opaque tuple structs anyways.
Default is essentially useless; to use it, you need a handle on the closure’s type… which you can’t get baring typeof or fn foo<T: Default>(_: T)… which defeat the purpose of Default in the first place.
The serde traits are also just as questionable, since you’d just be serializing the captures, for a type you can’t even name. How do you deserialize something like that? It’s not like you’ll be able to serialize code to send over the network to do RPCs, since the code inside a closure is part of its type. (I admit I am not an expert on how serde works, so feel free to strike that entire paragraph.)
tl;dr I question whether allowing closures to implement complicated traits based on their captures is a good design choice at all.
Note that despite this looking like a nested item, it’s not, instead it’s more like Java anonymous classes, and would share the “shared type-checking with parent function” property of Rust closures.