Skip to content

Commit ee71d12

Browse files
committed
Finish updating Components Macros chapter
1 parent c31c8b8 commit ee71d12

File tree

2 files changed

+197
-212
lines changed

2 files changed

+197
-212
lines changed

content/component-macros.md

Lines changed: 197 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Syntactically, all CGP components follow the same pattern. The pattern is
1313
roughly as follows:
1414

1515
```rust,ignore
16+
// Component name
17+
pub struct ActionPerformerComponent;
18+
1619
// Consumer trait
1720
pub trait CanPerformAction<GenericA, GenericB, ...>:
1821
ConstraintA + ConstraintB + ...
@@ -26,7 +29,8 @@ pub trait CanPerformAction<GenericA, GenericB, ...>:
2629
}
2730
2831
// Provider trait
29-
pub trait ActionPerformer<Context, GenericA, GenericB, ...>
32+
pub trait ActionPerformer<Context, GenericA, GenericB, ...>:
33+
IsProviderFor<ActionPerformerComponent, Context, (GenericA, GenericB, ...)>
3034
where
3135
Context: ConstraintA + ConstraintB + ...,
3236
{
@@ -38,9 +42,6 @@ where
3842
) -> Output;
3943
}
4044
41-
// Component name
42-
pub struct ActionPerformerComponent;
43-
4445
// Blanket implementation for consumer trait
4546
impl<Context, GenericA, GenericB, ...>
4647
CanPerformAction<GenericA, GenericB, ...> for Context
@@ -64,7 +65,8 @@ impl<Context, Component, GenericA, GenericB, ...>
6465
for Component
6566
where
6667
Context: ConstraintA + ConstraintB + ...,
67-
Component: DelegateComponent<ActionPerformerComponent>,
68+
Component: DelegateComponent<ActionPerformerComponent>
69+
+ IsProviderFor<ActionPerformerComponent, Context, (GenericA, GenericB, ...)>,
6870
Component::Delegate: ActionPerformer<Context, GenericA, GenericB, ...>,
6971
{
7072
fn perform_action(
@@ -78,7 +80,7 @@ where
7880
}
7981
```
8082

81-
## `cgp_component` Macro
83+
## `#[cgp_component]` Macro
8284

8385
With the repetitive pattern, it makes sense that we should be able to
8486
just define the consumer trait, and make use of Rust macros to generate
@@ -124,10 +126,7 @@ be omited. When omitted, the `context` field will default to `Context`,
124126
and the `name` field will default to `{provider}Component`.
125127
So the same example above could be simplified to:
126128

127-
128129
```rust,ignore
129-
use cgp::prelude::*;
130-
131130
#[cgp_component {
132131
provider: ActionPerformer,
133132
}]
@@ -143,8 +142,24 @@ pub trait CanPerformAction<GenericA, GenericB, ...>:
143142
}
144143
```
145144

145+
When only the provider name is specified, we can also omit the `key: value` syntax, and specify only the provider name:
146+
147+
```rust,ignore
148+
#[cgp_component(ActionPerformer)]
149+
pub trait CanPerformAction<GenericA, GenericB, ...>:
150+
ConstraintA + ConstraintB + ...
151+
{
152+
fn perform_action(
153+
&self,
154+
arg_a: ArgA,
155+
arg_b: ArgB,
156+
...
157+
) -> Output;
158+
}
159+
```
160+
146161

147-
## `delegate_components` Macro
162+
## `delegate_components!` Macro
148163

149164
In addition to the `cgp_component` macro, `cgp` also provides the
150165
`delegate_components!` macro that can be used to automatically implement
@@ -175,29 +190,177 @@ impl DelegateComponent<ComponentA> for TargetProvider {
175190
type Delegate = ProviderA;
176191
}
177192
193+
impl<Context, Params> IsProviderFor<ComponentA, Context, Params>
194+
for TargetProvider
195+
where
196+
ProviderA: IsProviderFor<ComponentA, Context, Params>,
197+
{
198+
}
199+
178200
impl DelegateComponent<ComponentB> for TargetProvider {
179201
type Delegate = ProviderB;
180202
}
181203
204+
impl<Context, Params> IsProviderFor<ComponentB, Context, Params>
205+
for TargetProvider
206+
where
207+
ProviderB: IsProviderFor<ComponentB, Context, Params>,
208+
{
209+
}
210+
182211
impl DelegateComponent<ComponentC1> for TargetProvider {
183212
type Delegate = ProviderC;
184213
}
185214
215+
impl<Context, Params> IsProviderFor<ComponentC1, Context, Params>
216+
for TargetProvider
217+
where
218+
ProviderC: IsProviderFor<ComponentC1, Context, Params>,
219+
{
220+
}
221+
186222
impl DelegateComponent<ComponentC2> for TargetProvider {
187223
type Delegate = ProviderC;
188224
}
225+
226+
impl<Context, Params> IsProviderFor<ComponentC2, Context, Params>
227+
for TargetProvider
228+
where
229+
ProviderC: IsProviderFor<ComponentC2, Context, Params>,
230+
{
231+
}
189232
```
190233

191234
The `delegate_components!` macro accepts an argument to an existing type,
192235
`TargetProvider`, which is expected to be defined outside of the macro.
193236
It is followed by an open brace, and contain entries that look like
194-
key-value pairs. For a key-value pair `ComponentA: ProviderA`, the type
195-
`ComponentA` is used as the component name, and `ProviderA` refers to
196-
the provider implementation.
237+
key-value pairs.
238+
239+
For a key-value pair `ComponentA: ProviderA`, the type `ComponentA` is used as the component name, and `ProviderA` refers to the provider implementation.
197240
When multiple keys map to the same value, i.e. multiple components are
198241
delegated to the same provider implementation, the array syntax can be
199242
used to further simplify the mapping.
200243

244+
Instead of defining the provider struct on our own, we can also instruct `delegate_components!` to also define the provider struct for us, by adding a `new` keyword in front:
245+
246+
```rust,ignore
247+
delegate_components! {
248+
new TargetProvider {
249+
ComponentA: ProviderA,
250+
ComponentB: ProviderB,
251+
[
252+
ComponentC1,
253+
ComponentC2,
254+
...
255+
]: ProviderC,
256+
}
257+
}
258+
```
259+
260+
## `#[cgp_provider]` Macro
261+
262+
When implementing a provider, the `#[cgp_provider]` macro needs to be used to automatically implement the `IsProviderFor` implementation, with all constraints within the `impl` block copied over.
263+
264+
Given a provider trait implementation with the pattern:
265+
266+
```rust,ignore
267+
pub struct Provider;
268+
269+
#[cgp_provider(ActionPerformerComponent)]
270+
impl<Context, GenericA, GenericB, ...>
271+
ActionPerformer<Context, GenericA, GenericB, ...>
272+
for Provider
273+
where
274+
Context: ConstraintA + ConstraintB + ...,
275+
Context::Assoc: ConstraintC + ConstraintD + ...,
276+
{ ... }
277+
```
278+
279+
`#[cgp_provider]` would generate an `IsProviderFor` implementation that follows the pattern:
280+
281+
```rust,ignore
282+
impl<Context, GenericA, GenericB, ...>
283+
IsProviderFor<ActionPerformerComponent, Context, GenericA, GenericB, ...>
284+
for Provider
285+
where
286+
Context: ConstraintA + ConstraintB + ...,
287+
Context::Assoc: ConstraintC + ConstraintD + ...,
288+
{ }
289+
```
290+
291+
If the component name for the provider trait follows the format `"{ProviderTraitName}Component"`, then the component name can be omitted in the attribute argument for `#[cgp_provider]`, simplifying the code to:
292+
293+
```rust,ignore
294+
pub struct Provider;
295+
296+
#[cgp_provider]
297+
impl<Context, GenericA, GenericB, ...>
298+
ActionPerformer<Context, GenericA, GenericB, ...>
299+
for Provider
300+
where
301+
Context: ConstraintA + ConstraintB + ...,
302+
Context::Assoc: ConstraintC + ConstraintD + ...,
303+
{ ... }
304+
```
305+
306+
Note, however, that the generated code would require the component type `"{ProviderTraitName}Component"` to be imported into scope. If the component name is not specified, IDEs like Rust Analyzer may not provide quick fix for auto importing the component. As a result, it may still be preferrable to include the component name attribute, especially when writing new code.
307+
308+
There is also a variant of the macro, `#[cgp_new_provider]`, which would also automatically define the struct for the provider. With that, the code can be defined with the `struct` definition omitted:
309+
310+
```rust,ignore
311+
#[cgp_new_provider]
312+
impl<Context, GenericA, GenericB, ...>
313+
ActionPerformer<Context, GenericA, GenericB, ...>
314+
for Provider
315+
where
316+
Context: ConstraintA + ConstraintB + ...,
317+
Context::Assoc: ConstraintC + ConstraintD + ...,
318+
{ ... }
319+
```
320+
321+
`#[cgp_new_provider]` is mainly useful in cases where a provider only implements one provider trait. When definining a provider with multiple provider trait implementations, it may be more clear to define the provider struct explicitly, or only use `#[cgp_new_provider]` for the first `impl` block of the provider.
322+
323+
## `check_components!` Macro
324+
325+
To help with debugging CGP code, the `check_components!` macro is provided to allow us to quickly write compile-time tests on the component wiring.
326+
327+
Given the following code pattern:
328+
329+
```rust,ignore
330+
check_components! {
331+
CanUseContext for Context {
332+
ComponentA,
333+
ComponentB,
334+
ComponentC: GenericA,
335+
[
336+
ComponentD,
337+
ComponentE,
338+
]: [
339+
(GenericB1, GenericB2, ...),
340+
(GenericC1, GenericC2, ...),
341+
],
342+
}
343+
}
344+
```
345+
346+
The following check trait would be generated:
347+
348+
```rust,ignore
349+
pub trait CanUseContext:
350+
CanUseComponent<ComponentA>
351+
+ CanUseComponent<ComponentB>
352+
+ CanUseComponent<ComponentC, GenericA>
353+
+ CanUseComponent<ComponentD, (GenericB1, GenericB2, ...)>
354+
+ CanUseComponent<ComponentD, (GenericC1, GenericC2, ...)>
355+
+ CanUseComponent<ComponentE, (GenericB1, GenericB2, ...)>
356+
+ CanUseComponent<ComponentE, (GenericC1, GenericC2, ...)>
357+
{}
358+
359+
impl CanUseContext for Context {}
360+
```
361+
362+
The `check_components!` macro allows the use of array syntax at either the key or value position, when there are multiple components that share the same set of generic parameters.
363+
201364
## Example Use
202365

203366
To illustrate how `cgp_component` and `delegate_components` can be
@@ -219,29 +382,19 @@ use serde::{Serialize, Deserialize};
219382

220383
// Component definitions
221384

222-
#[cgp_component {
223-
name: StringFormatterComponent,
224-
provider: StringFormatter,
225-
context: Context,
226-
}]
385+
#[cgp_component(StringFormatter)]
227386
pub trait CanFormatToString {
228387
fn format_to_string(&self) -> Result<String, Error>;
229388
}
230389

231-
#[cgp_component {
232-
name: StringParserComponent,
233-
provider: StringParser,
234-
context: Context,
235-
}]
390+
#[cgp_component(StringParser)]
236391
pub trait CanParseFromString: Sized {
237392
fn parse_from_string(raw: &str) -> Result<Self, Error>;
238393
}
239394

240395
// Provider implementations
241396

242-
pub struct FormatAsJsonString;
243-
244-
#[cgp_provider(StringFormatterComponent)]
397+
#[cgp_new_provider]
245398
impl<Context> StringFormatter<Context> for FormatAsJsonString
246399
where
247400
Context: Serialize,
@@ -251,9 +404,7 @@ where
251404
}
252405
}
253406

254-
pub struct ParseFromJsonString;
255-
256-
#[cgp_provider(StringParserComponent)]
407+
#[cgp_new_provider]
257408
impl<Context> StringParser<Context> for ParseFromJsonString
258409
where
259410
Context: for<'a> Deserialize<'a>,
@@ -279,31 +430,29 @@ impl HasProvider for Person {
279430

280431
delegate_components! {
281432
PersonComponents {
282-
StringFormatterComponent: FormatAsJsonString,
283-
StringParserComponent: ParseFromJsonString,
433+
StringFormatterComponent:
434+
FormatAsJsonString,
435+
StringParserComponent:
436+
ParseFromJsonString,
284437
}
285438
}
286439

287-
# let person = Person { first_name: "John".into(), last_name: "Smith".into() };
288-
# let person_str = r#"{"first_name":"John","last_name":"Smith"}"#;
289-
#
290-
# assert_eq!(
291-
# person.format_to_string().unwrap(),
292-
# person_str
293-
# );
294-
#
295-
# assert_eq!(
296-
# Person::parse_from_string(person_str).unwrap(),
297-
# person
298-
# );
440+
check_components! {
441+
CanUsePerson for Person {
442+
StringFormatterComponent,
443+
StringParserComponent,
444+
}
445+
}
299446
```
300447

301448
As we can see, the new code is significantly simpler and more readable than before.
302-
Using `cgp_component`, we no longer need to explicitly define the provider
303-
traits `StringFormatter` and `StringParser`, and the blanket implementations
304-
can be omitted. We also make use of `delegate_components!` on `PersonComponents`
305-
to delegate `StringFormatterComponent` to `FormatAsJsonString`, and
306-
`StringParserComponent` to `ParseFromJsonString`.
449+
Using `#[cgp_component]`, we no longer need to explicitly define the provider traits `StringFormatter` and `StringParser`, and the blanket implementations can be omitted.
450+
451+
With `#[cgp_new_provider]`, the `IsProviderFor` implementations for `FormatAsJsonString` and `ParseFromJsonString` are automatically implemented, together with the struct definitions.
452+
453+
We also make use of `delegate_components!` on `PersonComponents` to delegate `StringFormatterComponent` to `FormatAsJsonString`, and `StringParserComponent` to `ParseFromJsonString`.
454+
455+
Finally, the `check_components!` macro helps us check that we can in fact use `StringFormatterComponent` and `StringParserComponent` with the `Person` context.
307456

308457
## CGP Macros as Language Extension
309458

0 commit comments

Comments
 (0)