@@ -19,8 +19,8 @@ component interface, in addition to _consumer traits_ which are used for
19
19
consuming a component interface.
20
20
21
21
The separation of provider traits from consumer traits allows multiple context-generic
22
- provider implementations to be defined, bypassing Rust's trait system original restriction
23
- of forbidding overlapping implementations.
22
+ provider implementations to be defined, bypassing Rust's trait system's original restriction
23
+ that forbids overlapping implementations.
24
24
25
25
## Expressive Ways to Write Code
26
26
@@ -59,6 +59,175 @@ Since all CGP features work only at compile-time, it provides the same
59
59
_ zero-cost abstraction_ advantage as Rust. Applications do not have to sacrifice
60
60
any runtime overhead for using CGP in the code base.
61
61
62
+ # Hello World Example
63
+
64
+ We will demonstrate various concepts of CGP with a simple hello world example.
65
+
66
+ ## Greeter Component
67
+
68
+ First, we would import ` cgp ` and define a greeter component as follows:
69
+
70
+ ``` rust
71
+ use cgp :: prelude :: * ;
72
+
73
+ #[derive_component(GreeterComponent , Greeter <Context >)]
74
+ pub trait CanGreet {
75
+ fn greet (& self );
76
+ }
77
+ ```
78
+
79
+ The ` cgp ` crate provides all common constructs inside the ` prelude ` module,
80
+ which should be imported in many cases. The first CGP construct we use is
81
+ the ` derive_component ` macro, which generates additional constructs for
82
+ the greeter component. The macro target, ` CanGreet ` , is a _ consumer trait_
83
+ that is used similar to regular Rust traits, but is not for implementation.
84
+
85
+ The first macro argument, ` GreeterComponent ` , is used as the _ name_ of
86
+ the greeter component we defined. The second argument is used
87
+ to define a _ provider trait_ called ` Greeter ` , which is used for implementing
88
+ the greet component. ` Greeter ` has similar structure as the ` CanGreet ` ,
89
+ but with the implicit ` Self ` type replaced with the generic type ` Context ` .
90
+
91
+ ## Hello Greeter
92
+
93
+ With the greeter component defined, we would implement a hello greeter provider
94
+ as follows:
95
+
96
+ ``` rust
97
+ pub struct GreetHello ;
98
+
99
+ impl <Context > Greeter <Context > for GreetHello
100
+ where
101
+ Context : HasField <symbol ! ("name "), Field : Display >,
102
+ {
103
+ fn greet (context : & Context ) {
104
+ println! (
105
+ " Hello, {}!" ,
106
+ context . get_field (PhantomData :: <symbol! (" name" )>)
107
+ );
108
+ }
109
+ }
110
+ ```
111
+
112
+ The provider ` GreetHello ` is implemented as a struct, and implements
113
+ the provider trait ` Greeter ` . It is implemented as a
114
+ _ context-generic provider_ that can work with any ` Context ` type,
115
+ but with additional constraints (or dependencies) imposed on the
116
+ context.
117
+
118
+ In this example case, the constraint
119
+ ` HasField<symbol!("name"), Field: Display> ` means that ` GreetHello `
120
+ expects ` Context ` to be a struct with a field named ` name ` , with
121
+ the field type being any type that implements ` Display ` .
122
+
123
+ The trait ` HasField ` is a CGP getter trait for accessing fields in a
124
+ struct. The ` symbol! ` macro is used to convert any string literal
125
+ into types, so that they can be used as type argument. The
126
+ associated type ` Field ` is implemented as the type of the field in
127
+ the struct.
128
+
129
+ The ` HasField ` trait provides a ` get_field ` method,
130
+ which can be used to access a field value. The type
131
+ ` PhantomData::<symbol!("name")> ` is passed to ` get_field ` to infer
132
+ which field we want to read, in case if there are more than one
133
+ field in scope.
134
+
135
+ Notice that with the separation of provider trait from consumer trait,
136
+ multiple providers like ` GreetHello ` can _ all_ have generic implementation
137
+ over any ` Context ` , without causing any issue of overlapping implementation
138
+ imposed by Rust's trait system.
139
+
140
+ Additionally, the provider ` GreetHello ` can require additional
141
+ constraints from ` Context ` , without those constraints bein present
142
+ in the trait bound of ` CanGreet ` . This concept is sometimes known as
143
+ _ dependency injection_ , as extra dependencies are "injected" into
144
+ the provider through the context.
145
+
146
+ Compared to other languages, CGP can not only inject methods into
147
+ a provider, but also _ types_ , as we seen with the ` Field ` associated
148
+ type in ` HasField ` .
149
+
150
+ ## Person Context
151
+
152
+ Next, we will define a concrete context ` Person ` , and wire it up to
153
+ use ` GreetHello ` to implement ` CanGreet ` :
154
+
155
+ ``` rust
156
+ #[derive(HasField )]
157
+ pub struct Person {
158
+ pub name : String ,
159
+ }
160
+
161
+ pub struct PersonComponents ;
162
+
163
+ impl HasComponents for Person {
164
+ type Components = PersonComponents ;
165
+ }
166
+
167
+ delegate_components! {
168
+ PersonComponents {
169
+ GreeterComponent : GreetHello ,
170
+ }
171
+ }
172
+ ```
173
+
174
+ The ` Person ` context is defined to be a struct containing a ` name ` field,
175
+ which is of type ` String ` . The CGP macro ` derive(HasField) ` is used to
176
+ automatically implement ` Person: HasField<symbol!("name"), Field = String> ` ,
177
+ so that it can be used by ` GreetHello ` .
178
+
179
+ Additionally, we also define an empty struct ` PersonComponents ` , which
180
+ is used to wire up all the providers for ` Person ` . We implement the
181
+ CGP trait ` HasComponents ` for ` Person ` , which sets ` PersonComponents `
182
+ as its _ aggregated provider_ .
183
+
184
+ We use the CGP macro ` delegate_components ` to wire up the delegation of
185
+ providers for ` PersonComponent ` . The macro allows multiple components
186
+ to be listed in the body, in the form of ` ComponentName: Provider ` .
187
+ In this example, we only have one entry, which is to use ` GreetHello `
188
+ as the provider for ` GreeterComponent ` . Notice that this is where we
189
+ use the component name ` GreeterComponent ` , which was defined earlier
190
+ by ` derive_component ` .
191
+
192
+ With the expressive mapping of components to provider inside
193
+ ` delegate_components! ` , we can easily switch the implementation of
194
+ ` Greeter ` to another provider by making just one line of code change.
195
+
196
+ Note that CGP allows component wiring to be done _ lazily_ . This means
197
+ that any error such as unsatisfied dependencies will only be resolved
198
+ when we try to _ use_ the provider.
199
+
200
+ ## Calling Greet
201
+
202
+ Now that the wiring has been done, we can try to construct a ` Person `
203
+ and then call ` greet ` on it:
204
+
205
+ ``` rust
206
+ let person = Person {
207
+ name : " Alice" . into (),
208
+ };
209
+
210
+ // prints "Hello, Alice!"
211
+ person . greet ();
212
+ ```
213
+
214
+ If we try to build and run the above code, we will see that the code
215
+ compiles successfully, and the line "Hello, Alice!" is greeted on the
216
+ terminal.
217
+
218
+ The method ` greet ` is called from the consumer trait ` CanGreet ` , which
219
+ is implemented by ` Person ` via ` PersonComponents ` , which implements
220
+ ` Greeter ` via delegation of ` GreeterComponent ` to ` GreetHello ` ,
221
+ which implements ` Greeter ` given that ` Person ` implements
222
+ ` HasField<symbol!("name"), Field: Display> ` .
223
+
224
+ Hopefully by the end of this tutorial, you have gotten a sense of how
225
+ it is like to program in CGP.
226
+ There are a lot more to cover on how such wiring is done behind the scene
227
+ by CGP, and what else we can do with CGP.
228
+ You can continue and find out more by reading the book
229
+ [ Context-Generic Programming Patterns] ( https://patterns.contextgeneric.dev/ ) .
230
+
62
231
# Current Status
63
232
64
233
As of end of 2024, CGP is still in _ early-stage_ development, with many
0 commit comments