Skip to content

Commit 7997fa5

Browse files
authored
Merge pull request #345 from redbadger/hello-world-guide
update guide for hello world
2 parents 32d583b + a6151cd commit 7997fa5

File tree

2 files changed

+50
-111
lines changed

2 files changed

+50
-111
lines changed

docs/src/guide/hello_world.md

Lines changed: 37 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,14 @@ To turn it into an app, we need to implement the `App` trait from the
3030
```rust,noplayground
3131
use crux_core::App;
3232
33-
#[derive(Default)]
34-
pub struct Model;
35-
3633
impl App for Hello {}
3734
```
3835

3936
If you're following along, the compiler is now screaming at you that you're
40-
missing four associated types for the trait: `Event`, `Model`, `ViewModel` and
41-
`Capabilities`.
37+
missing five associated types for the trait`Event`, `Model`, `ViewModel`,
38+
`Capabilities`, (which will be deprecated soon, and can be set to `()`) and `Effect`.
4239

43-
Capabilities is the more complicated of them, and to understand what it does, we
44-
need to talk about what makes Crux different from most UI frameworks.
40+
The `Effect` associated type is worth understanding further, but in order to do that we need to talk about what makes Crux different from most UI frameworks.
4541

4642
## Side-effects and capabilities
4743

@@ -60,7 +56,7 @@ but it doesn't need to know or care whether that database is local or remote.
6056
That decision can even change as the application evolves, and be different on
6157
each platform. If you want to understand this better before we carry on, you can
6258
read a lot more about how side-effects work in Crux in the chapter on
63-
[capabilities](./capabilities.md).
59+
[Managed Effects](./effects.md).
6460

6561
To _ask_ the Shell for side effects, it will need to know what side effects it
6662
needs to handle, so we will need to declare them (as an enum). _Effects_ are
@@ -72,53 +68,32 @@ for requesting side-effects. We'll look at them in a lot more detail later.
7268
Let's start with the basics:
7369

7470
```rust,noplayground
75-
use crux_core::render::Render;
76-
77-
pub struct Capabilities {
78-
render: Render<Event>,
71+
use crux_core::{
72+
macros::effect,
73+
render::RenderOperation,
74+
};
75+
76+
#[effect]
77+
pub enum Effect {
78+
Render(RenderOperation),
7979
}
8080
```
8181

82-
As you can see, for now, we will use a single capability, `Render`, which is
83-
built into Crux and available from the `crux_core` crate. It simply tells the
84-
shell to update the screen using the latest information.
85-
86-
That means the core can produce a single `Effect`. It will soon be more than
87-
one, so we'll wrap it in an enum to give ourselves space. The `Effect` enum
88-
corresponds one to one to the `Capabilities` we're using, and rather than typing
89-
it (and its associated trait implementations) by hand and open ourselves to
90-
unnecessary mistakes, we can use the `crux_core::macros::Effect` derive macro.
82+
As you can see, for now, we will use a single capability, `crux_core::render`, which declares an `Operation` named `RenderOperation`, is built into Crux and is available from the `crux_core` crate. It simply tells the shell to update the screen using the latest information.
9183

92-
```rust,noplayground
93-
use crux_core::render::Render;
94-
use crux_core::macros::Effect;
95-
96-
#[derive(Effect)]
97-
pub struct Capabilities {
98-
render: Render<Event>,
99-
}
100-
```
84+
That means the core can produce a single `Effect`. It will soon be more than one, so we'll wrap it in an enum to give ourselves space. We'll also annotate our `Effect` enum with the `crux_core::macros::effect` attribute, which produces a _real_ `Effect` enum (which is very similar), one for FFI across the boundary to the shell, and various trait implementations and test helpers.
10185

102-
Other than the `derive` itself, we also need to link the effect to our app.
103-
We'll go into the detail of why that is in the [Capabilities](capabilities.md)
104-
section, but the basic reason is that capabilities need to be able to send the
105-
app the outcomes of their work.
86+
We also need to link the effect to our app. We'll go into the detail of why that is in the [Managed Effects](effects.md) section, but the basic reason is that capabilities need to be able to send the outcomes of their work back into the app.
10687

107-
You probably also noticed the `Event` type which capabilities are generic over,
108-
because they need to know the type which defines messages they can send back to
109-
the app. The same type is also used by the Shell to forward any user
110-
interactions to the Core, and in order to pass across the FFI boundary, it needs
111-
to be serializable. The resulting code will end up looking like this:
88+
You probably also noticed the `Event` type, which defines messages that can be sent back to the app. The same type is also used by the Shell to forward any user interactions to the Core, and in order to pass across the FFI boundary, it needs to be serializable. The resulting code will end up looking like this:
11289

11390
```rust,noplayground
114-
use crux_core::{render::Render, App};
115-
use crux_core::macros::Effect;
91+
use crux_core::{App, macros::effect, render::RenderOperation};
11692
use serde::{Deserialize, Serialize};
11793
118-
#[cfg_attr(feature = "typegen", derive(crux_core::macros::Export))]
119-
#[derive(Effect)]
120-
pub struct Capabilities {
121-
render: Render<Event>,
94+
#[effect]
95+
pub enum Effect {
96+
Render(RenderOperation),
12297
}
12398
12499
#[derive(Serialize, Deserialize)]
@@ -129,15 +104,9 @@ pub enum Event {
129104
#[derive(Default)]
130105
pub struct Hello;
131106
132-
impl App for Hello { ... }
107+
impl App for Hello {}
133108
```
134109

135-
In this example, we also invoke the `Export` derive macro, but only when the
136-
`typegen` feature is enabled — this is true in your `shared_types` library to
137-
generate the foreign types for the shell. For more detail see the
138-
[Shared core and types](../getting_started/core.md#create-the-shared-types-crate)
139-
guide.
140-
141110
Okay, that took a little bit of effort, but with this short detour out of the
142111
way and foundations in place, we can finally create an app and start
143112
implementing some behavior.
@@ -197,7 +166,7 @@ it's a fair amount of boiler plate code.
197166

198167
```admonish example
199168
You can find the full code for this part of the guide
200-
[here](https://github.com/redbadger/crux/blob/master/examples/simple_counter/shared/src/counter.rs)
169+
[here](https://github.com/redbadger/crux/blob/master/examples/simple_counter/shared/src/app.rs)
201170
```
202171

203172
Let's make things more interesting and add some behaviour. We'll teach the app
@@ -290,7 +259,7 @@ You can find the full code for this part of the guide [here](https://github.com/
290259
```
291260

292261
We'll add a simple integration with a counter API we've prepared at
293-
<https://crux-counter.fly.dev>. All it does is count up an down like our local
262+
<https://crux-counter.fly.dev>. All it does is count up and down like our local
294263
counter. It supports three requests
295264

296265
- `GET /` returns the current count
@@ -374,7 +343,7 @@ fn update(
374343
&self,
375344
event: Self::Event,
376345
model: &mut Self::Model,
377-
_caps: &Self::Capabilities,
346+
_caps: &(), // will be deprecated, so prefix with underscore for now
378347
) -> Command<Effect, Event> {
379348
match event {
380349
Event::Get => {
@@ -436,31 +405,18 @@ there's currently a technical limitation stopping us easily serializing
436405
sent by the Shell across the FFI boundary, which is the reason for the need to
437406
serialize in the first place — in a way, it is private to the Core.
438407

439-
Finally, let's get rid of those TODOs. We'll need to add crux_http in the
440-
`Capabilities` type, so that the `update` function has access to it.
441-
442-
```admonish note
443-
In the latest versions of `crux_http` (>= `v0.11.0`), this `Capabilities` type
444-
id being deprecated in favour of the new `Command` API (see the description of
445-
[Managed Effects](./effects.md) for more details).
446-
```
408+
Finally, let's get rid of those TODOs. We'll need to add a variant to the
409+
`Effect` enum, which holds the data for Http requests and responses.
410+
In the snippet, below, `HttpRequest` is an implementation (in `crux_http`) of the `Operation` trait, which links the request and response types together.
447411

448412
```rust,noplayground
449-
use crux_http::Http;
450-
451-
#[derive(Effect)]
452-
pub struct Capabilities {
453-
pub http: Http<Event>,
454-
pub render: Render<Event>,
413+
#[effect(typegen)]
414+
pub enum Effect {
415+
Render(RenderOperation),
416+
Http(HttpRequest),
455417
}
456418
```
457419

458-
This may seem like needless boilerplate, but it allows us to only use the
459-
capabilities we need and, more importantly, allow capabilities to be built by
460-
anyone. Later on, we'll also see that Crux apps [compose](composing.md), relying
461-
on each app's `Capabilities` type to declare its needs, and making sure the
462-
necessary capabilities exist in the parent app.
463-
464420
We can now implement those TODOs, so lets do it. We're using the latest `Command` API
465421
and so the `update` function will return a `Command` that has been created by
466422
the `crux_http` and `render` capabilities (rather than using the `caps` parameter
@@ -475,8 +431,9 @@ fn update(
475431
&self,
476432
event: Self::Event,
477433
model: &mut Self::Model,
478-
_caps: &Self::Capabilities,
479-
) -> Command<Effect, Event> { match event {
434+
_caps: &(), // will be deprecated, so prefix with underscore for now
435+
) -> Command<Effect, Event> {
436+
match event {
480437
Event::Get => Http::get(API_URL)
481438
.expect_json()
482439
.build()
@@ -526,7 +483,7 @@ fn update(
526483

527484
There's a few things of note. The first one is that the `.then_send` API at the end
528485
of each chain of calls to `crux_http` expects a function that wraps its argument
529-
(a `Result` of a http response) in a variant of `Event`. Fortunately, enum tuple
486+
(a `Result` of a HTTP response) in a variant of `Event`. Fortunately, enum tuple
530487
variants create just such a function, and we can use it. The way to read the
531488
call is "Send a get request, parse the response as JSON, which should be
532489
deserialized as a `Count`, and then call me again with `Event::Set` carrying the
@@ -543,12 +500,9 @@ You can find the the complete example, including the tests and shell implementat
543500
[in the Crux repo](https://github.com/redbadger/crux/blob/master/examples/counter/).
544501
It's interesting to take a closer look at the unit tests:
545502

546-
```admonish note
503+
```admonish example
547504
These tests are taken from the Counter example
548-
[implementation](https://github.com/redbadger/crux/blob/master/examples/counter/shared/src/app.rs)
549-
where we delegate to our own update function that does not take the `Capabilities`
550-
parameter, allowing us to test the app directly, without having to rely on
551-
the `AppTester`.
505+
[implementation](https://github.com/redbadger/crux/blob/master/examples/counter/shared/src/app.rs).
552506
```
553507

554508
```rust,noplayground

examples/hello_world/shared/src/app.rs

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// ANCHOR: app
2-
32
use crux_core::{
4-
render::{render, Render},
3+
macros::effect,
4+
render::{render, RenderOperation},
55
App, Command,
66
};
77
use serde::{Deserialize, Serialize};
88

9+
#[effect]
10+
pub enum Effect {
11+
Render(RenderOperation),
12+
}
13+
914
#[derive(Serialize, Deserialize)]
1015
pub enum Event {
1116
None,
@@ -19,34 +24,23 @@ pub struct ViewModel {
1924
data: String,
2025
}
2126

22-
#[derive(crux_core::macros::Effect)]
23-
#[allow(unused)]
24-
pub struct Capabilities {
25-
render: Render<Event>,
26-
}
27-
2827
#[derive(Default)]
2928
pub struct Hello;
3029

3130
impl App for Hello {
31+
type Effect = Effect;
3232
type Event = Event;
3333
type Model = Model;
3434
type ViewModel = ViewModel;
35-
type Capabilities = Capabilities;
36-
type Effect = Effect;
35+
type Capabilities = (); // will be deprecated, so use unit type for now
3736

3837
fn update(
3938
&self,
4039
_event: Self::Event,
4140
_model: &mut Self::Model,
42-
_caps: &Self::Capabilities,
41+
_caps: &(), // will be deprecated, so prefix with underscore for now
4342
) -> Command<Effect, Event> {
44-
// we no longer use the capabilities directly, but they are passed in
45-
// until the migration to managed effects with `Command` is complete
46-
// (at which point the capabilities will be removed from the `update`
47-
// signature). Until then we delegate to our own `update` method so that
48-
// we can test the app without needing to use AppTester.
49-
self.update(_event, _model)
43+
render()
5044
}
5145

5246
fn view(&self, _model: &Self::Model) -> Self::ViewModel {
@@ -56,15 +50,6 @@ impl App for Hello {
5650
}
5751
}
5852

59-
impl Hello {
60-
// note: this function can be moved into the `App` trait implementation, above,
61-
// once the `App` trait has been updated (as the final part of the migration
62-
// to managed effects with `Command`).
63-
fn update(&self, _event: Event, _model: &mut Model) -> Command<Effect, Event> {
64-
render()
65-
}
66-
}
67-
6853
// ANCHOR_END: app
6954

7055
// ANCHOR: test
@@ -74,11 +59,11 @@ mod tests {
7459

7560
#[test]
7661
fn hello_says_hello_world() {
77-
let hello = Hello::default();
62+
let hello = Hello;
7863
let mut model = Model;
7964

8065
// Call 'update' and request effects
81-
let mut cmd = hello.update(Event::None, &mut model);
66+
let mut cmd = hello.update(Event::None, &mut model, &());
8267

8368
// Check update asked us to `Render`
8469
cmd.expect_one_effect().expect_render();

0 commit comments

Comments
 (0)