Skip to content

Commit 5029582

Browse files
committed
Add hello world example
1 parent 937e46a commit 5029582

File tree

3 files changed

+172
-4
lines changed

3 files changed

+172
-4
lines changed

config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ build_search_index = true
1111
[markdown]
1212

1313
highlight_code = true
14+
highlight_theme = "kronuz"
1415

1516
[extra]
1617

content/_index.md

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ component interface, in addition to _consumer traits_ which are used for
1919
consuming a component interface.
2020

2121
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.
2424

2525
## Expressive Ways to Write Code
2626

@@ -59,6 +59,175 @@ Since all CGP features work only at compile-time, it provides the same
5959
_zero-cost abstraction_ advantage as Rust. Applications do not have to sacrifice
6060
any runtime overhead for using CGP in the code base.
6161

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+
62231
# Current Status
63232

64233
As of end of 2024, CGP is still in _early-stage_ development, with many

templates/_variables.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
/* Highlight text color of table of content */
1515
--toc-highlight-text-color: #c27e08;
1616
--toc-background-color: white;
17-
--code-color: #4a4a4a;
18-
--code-background-color: white;
1917
--shadow-color: #ddd;
2018
/* Font used for headers (h1 & h2) */
2119
--header-font-family: "Fira Sans", sans-serif;

0 commit comments

Comments
 (0)