You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/associated-types.md
+24-42Lines changed: 24 additions & 42 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -253,73 +253,55 @@ where
253
253
254
254
This example shows how CGP enables us to define context-generic providers that are not just generic over the context itself, but also over its associated types. Unlike traditional generic programming, where all generic parameters are specified positionally, CGP allows us to parameterize abstract types using _names_ via associated types.
255
255
256
-
## Defining Abstract Type Traits with `cgp_type!`
256
+
## Defining Abstract Type Traits with `#[cgp_type]`
257
257
258
-
The type traits `HasTimeType` and `HasAuthTokenType` share a similar structure, and as you define more abstract types, this boilerplate can become tedious. To streamline the process, the `cgp` crate provides the `cgp_type!` macro, which simplifies type trait definitions.
258
+
The type traits `HasTimeType` and `HasAuthTokenType` share a similar structure, and as you define more abstract types, this boilerplate can become tedious. To streamline the process, the `cgp` crate provides the `#[cgp_type]` macro, which simplifies type trait definitions.
259
259
260
-
Here's how you can define the same types with `cgp_type!`:
260
+
Here's how you can define the same types with `#[cgp_type]`:
261
261
262
262
```rust
263
263
# externcrate cgp;
264
264
#
265
265
usecgp::prelude::*;
266
266
267
-
#[cgp_type]
267
+
#[cgp_type {
268
+
provider:TimeTypeProvider,
269
+
}]
268
270
pubtraitHasTimeType {
269
271
typeTime:Eq+Ord;
270
272
}
271
273
272
-
#[cgp_type]
274
+
#[cgp_type {
275
+
provider:TimeTypeProvider,
276
+
}]
273
277
pubtraitHasAuthTokenType {
274
278
typeAuthToken;
275
279
}
276
280
```
277
281
278
-
The `cgp_type!` macro accepts the name of an abstract type, `$name`, along with any applicable constraints for that type. It then automatically generates the same implementation as the `cgp_component` macro: a consumer trait named `Has{$name}Type`, a provider trait named `Provide{$name}Type`, and a component name type named `${name}TypeComponent`. Each of the generated traits includes an associated type defined as `type $name: $constraints;`.
279
-
In addition, `cgp_type!` also derives some other implementations, which we'll explore in later chapters.
280
-
281
-
## Trait Minimalism
282
-
283
-
At first glance, it might seem overly verbose to define multiple type traits and require each to be explicitly included as a supertrait of a method interface. For instance, you might be tempted to consolidate the methods and types into a single trait, like this:
282
+
The `#[cgp_type]` macro works with a CGP trait that contains a single non-generic associated type. It is an extension over `#[cgp_component]`, and generate additional constructs that make it easy to work with abstract types in CGP. When no argument is given, `#[cgp_type]` would default to generate a provider with name `{Type}TypeProvider`, and a component name `{Type}TypeProviderComponent`, where `{Type}` is the name of the associated type in the trait. So the above example can be shortened to:
While this approach might seem simpler, it introduces unnecessary _coupling_ between
307
-
potentially unrelated types and methods. For example, an application implementing
308
-
token validation might delegate this functionality to an external microservice.
309
-
In such a case, it is redundant to require the application to specify a Time type that
310
-
it doesn’t actually use.
311
-
312
-
In practice, we find the practical benefits of defining many _minimal_ traits often
313
-
outweight any theoretical advantages of combining multiple items into one trait.
314
-
As we will demonstrate in later chapters, having traits that contain only one type
315
-
or method would also enable more advanced CGP patterns to be applied, including
316
-
the use of `cgp_type!` that we have just covered.
317
-
318
-
We encourage readers to embrace minimal traits without concern for theoretical overhead. However, during the early phases of a project, you might prefer to consolidate items to reduce cognitive overload while learning or prototyping. As the project matures, you can always refactor and decompose larger traits into smaller, more focused ones, following the techniques outlined in this book.
300
+
We will explore in a moment how using `#[cgp_type]` with a single associated type bring more convenience, as compared to alternative approaches.
319
301
320
302
## Impl-Side Associated Type Constraints
321
303
322
-
The minimalism philosophy of CGP extends to the constraints placed on associated types within type traits. Consider the earlier definition of `HasTimeType`:
304
+
The dependency-injection capabilities of CGP opens up new choices of how to design the abstract type interfaces. Consider the earlier definition of `HasTimeType`:
@@ -399,7 +381,7 @@ By applying constraints on the implementation side, we can conditionally require
399
381
400
382
In some cases, it can still be convenient to include constraints (e.g., `Debug`) directly on an associated type, especially if those constraints are nearly universal across providers. Additionally, current Rust error reporting often produces clearer error messages when constraints are defined at the associated type level, as opposed to being deferred to the implementation.
401
383
402
-
As a guideline, we recommend that readers begin by defining type traits without placing constraints on associated types, relying instead on implementation-side constraints wherever possible. However, readers may choose to apply global constraints to associated types when appropriate, particularly for simple and widely applicable traits like `Debug` and `Eq`.
384
+
Ultimately, CGP does not prevent its users from preferring one design approach over another. The minimalistic abstract type design is one that you will likely see often in CGP code, particularly in this book. However, do not hesitate to include addititional trait bounds based on your requirements and preferences!
403
385
404
386
## Type Providers
405
387
@@ -507,11 +489,11 @@ impl<Context> AuthTokenTypeProvider<Context> for UseStringAuthToken {
507
489
}
508
490
```
509
491
510
-
Compared to the newtype pattern, we can use plain `String` values directly, without wrapping them in a newtype struct. Contrary to common wisdom, in CGP, we place less emphasis on wrapping every domain type in a newtype. This is particularly true when most of the application is written in a context-generic style. The rationale is that abstract types and their accompanying interfaces already fulfill the role of newtypes by encapsulating and "protecting" raw values, reducing the need for additional wrapping.
492
+
## Comparison to Newtype Pattern
511
493
512
-
That said, readers are free to define newtypes and use them alongside abstract types. For beginners, this can be especially useful, as later chapters will explore methods to properly restrict access to underlying concrete types in context-generic code. Additionally, newtypes remain valuable when the raw values are also used in non-context-generic code, where access to the concrete types is unrestricted.
494
+
Abstract types serve as an alternative to the newtype pattern. Compared to the newtype pattern, we can use plain `String` values directly, without wrapping them in a newtype struct. Contrary to common wisdom, in CGP, we place less emphasis on wrapping every domain type in a newtype. This is particularly true when most of the application is written in a context-generic style. The rationale is that abstract types and their accompanying interfaces already fulfill the role of newtypes by encapsulating and "protecting" raw values, reducing the need for additional wrapping.
513
495
514
-
Throughout this book, we will primarily use plain types to implement abstract types, without additional newtype wrapping. However, we will revisit the comparison between newtypes and abstract types in later chapters, providing further guidance on when each approach is most appropriate.
496
+
Ultimately, there is no right or wrong whether one should use abstract types, new types, or both together. It is up to your own preference, experience, and requirements, to decide which approach is best suited for you. Just take note that abstract types will be a commonly used pattern in CGP, particularly in this book.
0 commit comments