|
1 |
| -# Linking Consumers with Providers |
| 1 | +# Linking Consumers with Providers |
| 2 | + |
| 3 | +In the [previous chapter](./provider-traits.md), we learned about how provider |
| 4 | +traits allow multiple overlapping implementations to be defined. However, if |
| 5 | +everything is implemented only as provider traits, it would be much more tedious |
| 6 | +having to determine which provider to use, at every time when we need to use the |
| 7 | +trait. To overcome this, we would need have _both_ provider traits and consumer |
| 8 | +traits, and have some ways to choose a provider when implementing a consumer trait. |
| 9 | + |
| 10 | +## Implementing Consumer Traits |
| 11 | + |
| 12 | +The simplest way to link a consumer trait with a provider is by implementing the |
| 13 | +consumer trait to call a chosen provider. Consider the `StringFormatter` example |
| 14 | +of the previous chapter, we would implement `CanFormatString` for a `Person` |
| 15 | +context as follows: |
| 16 | + |
| 17 | +```rust |
| 18 | +use core::fmt::{self, Display}; |
| 19 | + |
| 20 | +pub trait CanFormatString { |
| 21 | + fn format_string(&self) -> String; |
| 22 | +} |
| 23 | + |
| 24 | +pub trait StringFormatter<Context> { |
| 25 | + fn format_string(context: &Context) -> String; |
| 26 | +} |
| 27 | + |
| 28 | +#[derive(Debug)] |
| 29 | +pub struct Person { |
| 30 | + pub first_name: String, |
| 31 | + pub last_name: String, |
| 32 | +} |
| 33 | + |
| 34 | +impl Display for Person { |
| 35 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 36 | + write!(f, "{} {}", self.first_name, self.last_name) |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +impl CanFormatString for Person { |
| 41 | + fn format_string(&self) -> String { |
| 42 | + FormatStringWithDisplay::format_string(self) |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +let person = Person { first_name: "John".into(), last_name: "Smith".into() }; |
| 47 | + |
| 48 | +assert_eq!(person.format_string(), "John Smith"); |
| 49 | +# |
| 50 | +# pub struct FormatStringWithDisplay; |
| 51 | +# |
| 52 | +# impl<Context> StringFormatter<Context> for FormatStringWithDisplay |
| 53 | +# where |
| 54 | +# Context: Display, |
| 55 | +# { |
| 56 | +# fn format_string(context: &Context) -> String { |
| 57 | +# format!("{}", context) |
| 58 | +# } |
| 59 | +# } |
| 60 | +``` |
| 61 | + |
| 62 | +To recap the previous chapter, we have a consumer trait `CanFormatString` |
| 63 | +and a provider trait `StringFormatter`. There are two example providers that |
| 64 | +implemenent `StringFormatter` - `FormatStringWithDisplay` which formats strings |
| 65 | +using `Display`, and `FormatStringWithDebug` which formats strings using `Debug`. |
| 66 | +In addition to that, we implement `CanFormatString` for the `Person` context |
| 67 | +by forwarding the call to `FormatStringWithDisplay`. |
| 68 | + |
| 69 | +By doing so, we effectively "bind" the `StringFormatter` provider for the |
| 70 | +`Person` context to `FormatStringWithDisplay`. With that, any time a consumer |
| 71 | +code calls `person.format_string()`, it would automatically format the context |
| 72 | +using `Display`. |
| 73 | + |
| 74 | +Thanks to the decoupling of providers and consumers, a context like `Person` |
| 75 | +can freely choose between multiple providers, and link them with relative ease. |
| 76 | +Similarly, the provider trait allows multiple context-generic providers such as |
| 77 | +`FormatStringWithDisplay` and `FormatStringWithDebug` to co-exist. |
| 78 | + |
| 79 | +## Blanket Consumer Trait Implementation |
| 80 | + |
| 81 | +In the previous section, we manually implemented `CanFormatString` for `Person` |
| 82 | +with an explicit call to `FormatStringWithDisplay`. Although the implementation |
| 83 | +is relatively short, it can become tedious if we make heavy use of provider traits, |
| 84 | +which would require us to repeat the same pattern for every trait. |
| 85 | + |
| 86 | +To simplify this further, we can make use of _blanket implementations_ to |
| 87 | +automatically delegate the implementation of _all_ consumer traits to one |
| 88 | +chosen provider. We would define the blanket implementation for `CanFormatString` |
| 89 | +as follows: |
| 90 | + |
| 91 | +```rust |
| 92 | +pub trait HasComponents { |
| 93 | + type Components; |
| 94 | +} |
| 95 | + |
| 96 | +pub trait CanFormatString { |
| 97 | + fn format_string(&self) -> String; |
| 98 | +} |
| 99 | + |
| 100 | +pub trait StringFormatter<Context> { |
| 101 | + fn format_string(context: &Context) -> String; |
| 102 | +} |
| 103 | + |
| 104 | +impl<Context> CanFormatString for Context |
| 105 | +where |
| 106 | + Context: HasComponents, |
| 107 | + Context::Components: StringFormatter<Context>, |
| 108 | +{ |
| 109 | + fn format_string(&self) -> String { |
| 110 | + Context::Components::format_string(self) |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +First of all, we define a new `HasComponents` trait that contains an associated |
| 116 | +type `Components`. The `Components` type would be specified by a context to |
| 117 | +choose a provider that it would use to forward all implementations of consumer |
| 118 | +traits. Following that, we add a blanket implementation for `CanFormatString`, |
| 119 | +which would be implemented for any `Context` that implements `HasComponents`, |
| 120 | +provided that `Context::Components` implements `StringFormatter<Context>`. |
| 121 | + |
| 122 | +To explain in simpler terms - if a context has a provider that implements |
| 123 | +a provider trait for that context, then the consumer trait for that context |
| 124 | +is also automatically implemented. |
| 125 | + |
| 126 | +With the new blanket implementation in place, we can now implement `HasComponents` |
| 127 | +for the `Person` context, and it would now help us to implement `CanFormatString` |
| 128 | +for free: |
| 129 | + |
| 130 | +```rust |
| 131 | +use core::fmt::{self, Display}; |
| 132 | + |
| 133 | +#[derive(Debug)] |
| 134 | +pub struct Person { |
| 135 | + pub first_name: String, |
| 136 | + pub last_name: String, |
| 137 | +} |
| 138 | + |
| 139 | +impl Display for Person { |
| 140 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 141 | + write!(f, "{} {}", self.first_name, self.last_name) |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +impl HasComponents for Person { |
| 146 | + type Components = FormatStringWithDisplay; |
| 147 | +} |
| 148 | + |
| 149 | +let person = Person { first_name: "John".into(), last_name: "Smith".into() }; |
| 150 | + |
| 151 | +assert_eq!(person.format_string(), "John Smith"); |
| 152 | +# |
| 153 | +# pub trait HasComponents { |
| 154 | +# type Components; |
| 155 | +# } |
| 156 | +# |
| 157 | +# pub trait CanFormatString { |
| 158 | +# fn format_string(&self) -> String; |
| 159 | +# } |
| 160 | +# |
| 161 | +# pub trait StringFormatter<Context> { |
| 162 | +# fn format_string(context: &Context) -> String; |
| 163 | +# } |
| 164 | +# |
| 165 | +# impl<Context> CanFormatString for Context |
| 166 | +# where |
| 167 | +# Context: HasComponents, |
| 168 | +# Context::Components: StringFormatter<Context>, |
| 169 | +# { |
| 170 | +# fn format_string(&self) -> String { |
| 171 | +# Context::Components::format_string(self) |
| 172 | +# } |
| 173 | +# } |
| 174 | +# |
| 175 | +# pub struct FormatStringWithDisplay; |
| 176 | +# |
| 177 | +# impl<Context> StringFormatter<Context> for FormatStringWithDisplay |
| 178 | +# where |
| 179 | +# Context: Display, |
| 180 | +# { |
| 181 | +# fn format_string(context: &Context) -> String { |
| 182 | +# format!("{}", context) |
| 183 | +# } |
| 184 | +# } |
| 185 | +``` |
| 186 | + |
| 187 | +Compared to before, the implementation of `HasComponents` is much shorter than |
| 188 | +implementing `CanFormatString` directly, since we only need to specify the provider |
| 189 | +type without any function definition. |
| 190 | + |
| 191 | +At the moment, because the `Person` context only implements one consumer trait, we |
| 192 | +can set `FormatStringWithDisplay` directly as `Person::Components`. However, if there |
| 193 | +are other consumer traits that we would like to use with `Person`, we would need to |
| 194 | +define `Person::Components` with a separate provider that implements multiple provider |
| 195 | +traits. This will be covered in the next chapter, which we would talk about how to |
| 196 | +link multiple providers of different provider traits together. |
| 197 | + |
| 198 | +## Component System |
| 199 | + |
| 200 | +You may have noticed that the trait for specifying the provider for a context is called |
| 201 | +`HasComponents` instead of `HasProviders`. This is to generalize the idea of a pair of |
| 202 | +consumer trait and provider trait working together, forming a _component_. |
| 203 | + |
| 204 | +In context-generic programming, we use the term _component_ to refer to a consumer-provider |
| 205 | +trait pair. The consumer trait and the provider trait are linked together through blanket |
| 206 | +implementations and traits such as `HasComponents`. These constructs working together to |
| 207 | +form the basis for a _component system_ for CGP. |
0 commit comments