|
| 1 | +# Blanket Traits |
| 2 | + |
| 3 | +The `#[blanket_trait]` macro is provided to simplify the implementation of traits that are meant to only contain blanket implementations and not implemented by others. Without the macro, we would first need to define the trait, and then define the blanket `impl` for that trait. But with `#[blanket_trait]`, the two definitions can be merged into one. |
| 4 | + |
| 5 | +## Combining Multiple Traits |
| 6 | + |
| 7 | +The first use of `#[blanket_trait]` is to define trait aliases that combine multiple traits into one. |
| 8 | + |
| 9 | +For example, if we want to combine a `Foo` trait and a `Bar` trait together to become a `FooBar` trait, a manual approach would require the following definition: |
| 10 | + |
| 11 | +```rust |
| 12 | +pub trait FooBar: Foo + Bar {} |
| 13 | + |
| 14 | +impl<Context> FooBar for Context |
| 15 | +where |
| 16 | + Context: Foo + Bar, |
| 17 | +{} |
| 18 | +``` |
| 19 | + |
| 20 | +But with the use of `#[blanket_trait]`, the whole definition can be shorten to: |
| 21 | + |
| 22 | +```rust |
| 23 | +#[blanket_trait] |
| 24 | +pub trait FooBar: Foo + Bar {} |
| 25 | +``` |
| 26 | + |
| 27 | +Behind the scene, `#[blanket_trait]` generates a blanket implementation that can minimally satisfy the constraint of the trait definition. |
| 28 | + |
| 29 | +## Blanket Methods |
| 30 | + |
| 31 | +We can also use `#[blanket_trait]` to provide blanket implementations that contain methods, by implementing the given methods in the trait. |
| 32 | + |
| 33 | +For example, suppose we want to implement a blanket implementation of `foobar()` that calls both `foo()` and `bar()`, a manual implementation would require the following to be written: |
| 34 | + |
| 35 | + |
| 36 | +```rust |
| 37 | +pub trait FooBar: Foo + Bar { |
| 38 | + fn foobar(); |
| 39 | +} |
| 40 | + |
| 41 | +impl<Context> FooBar for Context |
| 42 | +where |
| 43 | + Context: Foo + Bar, |
| 44 | +{ |
| 45 | + fn foobar(&self) { |
| 46 | + self.foo().bar() |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +With `#[blanket_trait]`, the same definition can be shorten to: |
| 52 | + |
| 53 | +```rust |
| 54 | +#[blanket_trait] |
| 55 | +pub trait FooBar: Foo + Bar { |
| 56 | + fn foobar(&self) { |
| 57 | + self.foo().bar() |
| 58 | + } |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +## Associated Type Alias |
| 63 | + |
| 64 | +The use of `#[blanket_trait]` can allow us to also more easily define associated type aliases that can be used to simplify the reference to a given abstract type, especially when it is parameterized through a generic type. |
| 65 | + |
| 66 | +For example, given the following type trait: |
| 67 | + |
| 68 | +```rust |
| 69 | +#[cgp_type] |
| 70 | +pub trait HasChainTypeAt<I> { |
| 71 | + type Chain; |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +It can be quite tedious to refer to the `Chain` context at each index, such as `<Context as HasChainTypeAt<Index<0>>>::Chain` and `<Context as HasChainTypeAt<Index<1>>>::Chain`. We may want to define _associated type aliases_ `FirstChain` and `SecondChain`, so that we can refer to the first and second chains more easily in our code. A manual implementation of such alias would be as follows: |
| 76 | + |
| 77 | +```rust |
| 78 | +pub trait HasTwoChains: |
| 79 | + HasChainTypeAt<Index<0>, Chain = Self::FirstChain> |
| 80 | + + HasChainTypeAt<Index<1>, Chain = Self::SecondChain> |
| 81 | +{ |
| 82 | + type FirstChain; |
| 83 | + |
| 84 | + type SecondChain; |
| 85 | +} |
| 86 | + |
| 87 | +impl<Context, FirstChain, SecondChain> |
| 88 | + HasTwoChains for Context |
| 89 | +where |
| 90 | + Context: |
| 91 | + HasChainTypeAt<Index<0>, Chain = FirstChain> |
| 92 | + + HasChainTypeAt<Index<1>, Chain = SecondChain>, |
| 93 | +{ |
| 94 | + type FirstChain = FirstChain; |
| 95 | + |
| 96 | + type SecondChain = SecondChain; |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +The way we understand how the associated type alias works is as follows: |
| 101 | + |
| 102 | +- The trait `HasTwoChains` contains two associated types, `FirstChain` and `SecondChain`, which are unspecified at the trait definition. |
| 103 | +- The trait has a supertrait `HasChainTypeAt<Index<0>, Chain = Self::FirstChain>`. This means that when the trait is used, the type `<Self as HasChainTypeAt<Index<0>>>::Chain` _must_ be the same as `Self::FirstChain`. |
| 104 | +- Similarly, the supertrait `HasChainTypeAt<Index<0>, Chain = Self::FirstChain>` indicates that `<Self as HasChainTypeAt<Index<1>>>::Chain` must be the same as `Self::SecondChain`. |
| 105 | +- In the blanket implementation, we introduce two generic parameters, `FirstChain` and `SecondChain` |
| 106 | +- We bind `FirstChain` in `HasChainTypeAt<Index<0>, Chain = FirstChain>`, meaning that we now just refer to `<Context as HasChainTypeAt<Index<0>>>::Chain` as `FirstChain`. |
| 107 | +- Similarly, with the binding of `SecondChain` in `HasChainTypeAt<Index<1>, Chain = SecondChain>`, it means that we now just refer to `<Context as HasChainTypeAt<Index<1>>>::Chain` as `SecondChain`. |
| 108 | +- We then define the associated type `FirstChain` to be just `FirstChain`, and similarly for `SecondChain`. |
| 109 | +- This means that effectively, we are just aliasing the associated types with new names. |
| 110 | + |
| 111 | +In case if it is still confusing to you, the same blanket `impl` above can be rewritten as follows, which may be more verbose but perhaps less confusing: |
| 112 | + |
| 113 | +```rust |
| 114 | +impl<Context> HasTwoChains for Context |
| 115 | +where |
| 116 | + Context: |
| 117 | + HasChainTypeAt<Index<0>> |
| 118 | + + HasChainTypeAt<Index<1>>, |
| 119 | +{ |
| 120 | + type FirstChain = <Context as HasChainTypeAt<Index<0>>>::Chain; |
| 121 | + |
| 122 | + type SecondChain = <Context as HasChainTypeAt<Index<1>>>::Chain; |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +Basically, the two implementations are equivalent, just written in different styles. |
| 127 | + |
| 128 | +But the key takeaway is that with `#[blanket_trait]`, we do not need to hand write any of the blanket impls, and it would instead be automatically derived for us: |
| 129 | + |
| 130 | + |
| 131 | +```rust |
| 132 | +#[blanket_trait] |
| 133 | +pub trait HasTwoChains: |
| 134 | + HasChainTypeAt<Index<0>, Chain = Self::FirstChain> |
| 135 | + + HasChainTypeAt<Index<1>, Chain = Self::SecondChain> |
| 136 | +{ |
| 137 | + type FirstChain; |
| 138 | + |
| 139 | + type SecondChain; |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +The main requirement for the use of associated type aliases is that the associated type must be bound to some other associated type inside the supertraits. In this case, we bind both `Self::FirstChain` and `Self::SecondChain` in the trait alias, and so `#[blanket_trait]` is able to generate the blanket implementation correctly for us. |
0 commit comments