## Static dispatch Support for enum dispatching. 1. `core` traits. 2. `std` traits. 3. `custom` traits. Terminology: - Candidate - is a contender that have the potential of being selected for dispatch. - Dispatchelor - is the candidate that has been selected for dispach. - Arbitor - is the one who has the final decision in the selecting process. First of all, there are some questions about what can be considered dispatchable. When we have `group` of arity one, we don't have any problems knowing what field is dispatchable, because there is only one possible candidate. But any other arity, even a nullary, can give us some problem because there can be disputes about which field should be considered for dispatch. First lets consider naming some of the problems. - Twin problem - They are literally the same. - Duck problem - When they differ structurally/nominally, but behaves similarly/identically. ### Building a candidate list of potential `dispatchelors`. Firsly, we should do the naive approach and just add all whom fit the behavior (has implemented the trait). After that, we need to start deciding which of them should take precedence, because there will be disputes about which field is more suitable to dispatch. To be able to dispatch a variant containing multiple fields--implementing the same trait (i.e. different but same), we need some kind of `arbitor` that does this for us. 1. User bias - like if they have explicitly written, "I want THIS to be dispatched!". 2. Candidate occurance - how often does a candidate occur in other variants. 3. Variant arity - if we have a duck problem, we select based on previous unary variant `dispatchelors`. 4. First come First served - we pick the first one in the list. ```rust trait Trait { fn run(&self) { println!("hello") } } struct Adam; struct Eva; impl Trait for Adam {} impl Trait for Eva {} // NOTE: When Penum tries to match a variant with a pattern fragment, it does so by partial order. #[penum( // # Any pattern fragment that is of arity one (unary variants) won't have any problems // getting added as a `dispatchelor`. They are the only candidate we have.. (T) | { name: T } | // # Here we have a tuple containing one concrete type and one generic param. // Firstly, the only thing Penum knows right now is that `T` // implements `Trait`. It does not know that `Eva` also // implements `Trait`--because it hasn't been specified. // // # The only way Penum can know about `Eva` implementations is: // 1. If we have have an explicit bound telling us about it. // 2. If one of the variant matching `T` gets substituted by `Eva`. // // # Selecting a `dispatchelor` here should be easy as long as `T` != `Eva`, // for all variants, we know that we should dispatch `T` over `Eva`. // This is because we don't know anything about Eva as long as `T` // isn't being substituted by it. So we can rule out `Eva` as a `dispatchelor` (Eva, T) | // # Here we have a tuple containing a `placeholder` and a `generic` param. // This one is also a little tricky because we can end up getting // (Eva, Eva), which would cause a dispute because both are valid // candidates. To be able to choose a `dispatchelor`, one would have to // ask the arbitor. (_, T) | // # Here we have a tuple containing two generic params. // This one is tricky, because I don't know if this pattern fragment even can be matched given // the previous pattern fragment. This is because Penum has a partial match order, and because // `_` is a wildcard, it will catch all variants that also will match this one. (T, U) | // # Here we have a struct containing two generic params. // This pattern fragment should not have the same problem as the pattern fragment above. // Here we actually know that these two fields will be in a dispute, before any variants are // present. To be able to choose a `dispatchelor`, one would have to ask the arbitor. { a: T, b: U } where T: Trait, U: Trait )] enum dispatched { V1(Eva, Adam), V2(Adam, Adam), } ``` ### Introduce new syntax Introducing a symbol marker that is used to mark traits as dispachable. I'm thinking about `^` being that symbol. But I don't know if it should be inside a pattern fragment or where clause. It would make more sense to have it in the where clause for a trait than for a generic param in a pattern fragment. This is because a generic param could have multiple traits and then it would be difficult to know which one should be dispatched.. 1. This feels weird because we would have to declare it for every pattern fragment, making it tedius because we would have to then also mark `(T)` as dispatchable. ```rust #[penum( (T) | (^T, U) where T: Trait + Tiart, U: Trait )] ``` 2. This makes more sense because we are saying that all `T`s are dispatchable. But it's still a little hard to understand which trait is being dispatched. This could actually be a short hand for: All `T`s traits should be dispatchable. ```rust #[penum( (T) | (T, U) where ^T: Trait + Tiart, U: Trait )] ``` 3. This seems like the more natural choice because we are very selective about what trait should be dispatchable; and even if another generic also has the same trait bound, it won't be considered dispatchable because it's not marked with `^`. ```rust #[penum( (T) | (T, U) where T: ^Trait + Tiart, U: Trait )] ``` Another thing is that for something to be dispachable, all variants must include the generic with the marked trait? It should also be possible to use `impl ^Trait` to become a dispatchable candidate. Knowing that this syntax makes it a little confusing towards unmatched variants. e.g. ```rust struct Random; #[penum( // # Given this impl trait type and the variants: // - Sadly, because of the "dum" polymorphic builder, V2's first argument `Random` will be // expected to implement AsRef.But we already know that it does not, if we had core/std trait // knowledge. // - i.e it would be possible to infer that V2s second argument could be a valid dispatcher // because of V1s first argument. But this might lead to some sort of unsoundness given // that the order and position should matter. How it should be implemented then is by a // where clause. // // e.g. (..) where String: ^AsRef // // This is so much cleaner. (impl ^AsRef, ..) | (..) )] enum dispatched { // First argument matches our impl trait bound, and because it's prefixed with `^`, we give mark // variant with a dispatch arm. V1(String, Random), // Now, because V2(Random, String), V3(Random, String, String), } ``` ### Dispatch semantics One of the problems with implementing some sort of derived dispatch solution for enums is that most of the time the variants aren't even dispatchable. When a variant isn't dispatchable, we often have to look at the trait method signature to be able to figure out what we can do. Without knowing any specifics about the overall program (e.g knowing that we have implemented something for a type in another module), so that the only option for us is to deduce the meaning through semantically understanding the penum expression and the subject (that being the enum itself). - The type and its position in the pattern. - So for each variant that matches a certain pattern where one or more of the trait bound members are marked for dispatch, we build a candidate list where each candidate contains information about what variant it matched, what position it matched on, and what type it matched on. Given this list, we can better understand how we would solve problems like: a. Variants without a dispatch match (e.g the might have matched for another pattern that didn't contain a dispatch member). b. Variants that match but where the type differs from previous matches. - The trait signature. - This gives us information about how the actual method we are dispaching should handle variants that can't be dispatched. - Method signature without a return type can be handled by just matching the variant with a Unit type. - Method signature with a return type is handled differently depending on the return type implementations. - Return type implements Default and the value is owned: - Then we just match with `Default::default()` - Return type doesn't implements Default and the value is owned: - Then we might do a Option wrap and return None - Return type implements Default and but it's a reference: - Can the type be const evaluated in a static declaration, then we of course do that. - If it cannot be const evaluated in a static declaration, then we might need to use `LazyCell`. - Return type doesn't implement Default -> Then we handle it by the use of semantic analyzing - If it's a core/std lib type, could we somehow know how to handle it? - If we don't know anything about the type, should we a. Option wrap it for the user? b. Add panic handlers? c. Try to It should also be possible to use `impl ^Trait` to become a dispatchable candidate. Knowing that this syntax makes it a little confusing towards unmatched variants. e.g. ```rust // Question is, should we always assume that a dispatch candidate always implements Default, or // should we be required to specify it?. // Feels like it should be specified. // If we don't specify it, should we then just Option wrap it? #[penum( (T, U) where T: ^AsRef + Default )] enum Disp { V1(String, i32), V2 } impl AsRef for Disp where String: Default { fn as_ref(&self) -> &str { match Self { Self::V1(arg1, ..) => AsRef::::as_ref(self), Self::V2 => Default::default() } } } // ----------------------------------------------- // This won't work.. #[penum( (T, ..) where T: ^AsRef )] enum Disp { V1(String, i32), V2, } impl<'a> AsRef> for Disp where Self: 'a, String: Default, { fn as_ref(&self) -> &Option<&str> { match self { Self::V1(arg1, ..) => &Some(AsRef::as_ref(arg1)), Self::V2 => &None, } } } ```