crux_core/lib.rs
1#![deny(clippy::pedantic)]
2//! Cross-platform app development in Rust
3//!
4//! Crux helps you share your app's business logic and behavior across mobile (iOS and Android) and web,
5//! as a single, reusable core built with Rust.
6//!
7//! Unlike React Native, the user interface layer is built natively, with modern declarative UI frameworks
8//! such as Swift UI, Jetpack Compose and React/Vue or a WASM based framework on the web.
9//!
10//! The UI layer is as thin as it can be, and all other work is done by the shared core.
11//! The interface with the core has static type checking across languages.
12//!
13//! ## Getting Started
14//!
15//! Crux applications are split into two parts: a Core written in Rust and a Shell written in the platform
16//! native language (e.g. Swift or Kotlin). It is also possible to use Crux from Rust shells.
17//! The Core architecture is based on [Elm architecture](https://guide.elm-lang.org/architecture/).
18//!
19//! Quick glossary of terms to help you follow the example:
20//!
21//! * Core - the shared core written in Rust
22//!
23//! * Shell - the native side of the app on each platform handling UI and executing side effects
24//!
25//! * App - the main module of the core containing the application logic, especially model changes
26//! and side-effects triggered by events. An App can delegate to child apps, mapping Events and Effects.
27//!
28//! * Event - main input for the core, typically triggered by user interaction in the UI
29//!
30//! * Model - data structure (typically tree-like) holding the entire application state
31//!
32//! * View model - data structure describing the current state of the user interface
33//!
34//! * Effect - A side-effect the core can request from the shell. This is typically a form of I/O or similar
35//! interaction with the host platform. Updating the UI is considered an effect.
36//!
37//! * Command - A description of a side-effect or a sequence of side-effects to be executed by the shell.
38//! Commands can be combined (synchronously with combinators, or asynchronously with Rust async) to run
39//! sequentially or concurrently, or any combination thereof.
40//!
41//! * Capability - A user-friendly API used to create Commands for a specific effect type (e.g. HTTP)
42//!
43//!
44//! Below is a minimal example of a Crux-based application Core:
45//!
46//! ```rust
47//!// src/app.rs
48//!use crux_core::{render::{self, RenderOperation}, App, macros::effect, Command};
49//!use serde::{Deserialize, Serialize};
50//!
51//!// Model describing the application state
52//!#[derive(Default)]
53//!struct Model {
54//! count: isize,
55//!}
56//!
57//!// Event describing the actions that can be taken
58//!#[derive(Serialize, Deserialize)]
59//!pub enum Event {
60//! Increment,
61//! Decrement,
62//! Reset,
63//!}
64//!
65//!// Effects the Core will request from the Shell
66//!#[effect(typegen)]
67//!pub enum Effect {
68//! Render(RenderOperation),
69//!}
70//!
71//!#[derive(Default)]
72//!struct Hello;
73//!
74//!impl App for Hello {
75//! // Use the above Event
76//! type Event = Event;
77//! // Use the above Model
78//! type Model = Model;
79//! type ViewModel = String;
80//! // Use the above generated Effect
81//! type Effect = Effect;
82//!
83//! fn update(&self, event: Event, model: &mut Model) -> Command<Effect, Event> {
84//! match event {
85//! Event::Increment => model.count += 1,
86//! Event::Decrement => model.count -= 1,
87//! Event::Reset => model.count = 0,
88//! };
89//!
90//! // Request a UI update
91//! render::render()
92//! }
93//!
94//! fn view(&self, model: &Model) -> Self::ViewModel {
95//! format!("Count is: {}", model.count)
96//! }
97//!}
98//! ```
99//!
100//! ## Integrating with a Shell
101//!
102//! To use the application in a user interface shell, you need to expose the core interface for FFI.
103//! This "plumbing" will likely be simplified with macros in the future versions of Crux.
104//!
105//! ```rust,ignore
106//! // src/lib.rs
107//! pub mod app;
108//!
109//! use lazy_static::lazy_static;
110//! use wasm_bindgen::prelude::wasm_bindgen;
111//!
112//! pub use crux_core::bridge::{Bridge, Request};
113//! pub use crux_core::Core;
114//! pub use crux_http as http;
115//!
116//! pub use app::*;
117//!
118//! uniffi_macros::include_scaffolding!("hello");
119//!
120//! lazy_static! {
121//! static ref CORE: Bridge<Effect, App> = Bridge::new(Core::new::<Capabilities>());
122//! }
123//!
124//! #[wasm_bindgen]
125//! pub fn process_event(data: &[u8]) -> Vec<u8> {
126//! CORE.process_event(data)
127//! }
128//!
129//! #[wasm_bindgen]
130//! pub fn handle_response(id: u32, data: &[u8]) -> Vec<u8> {
131//! CORE.handle_response(id, data)
132//! }
133//!
134//! #[wasm_bindgen]
135//! pub fn view() -> Vec<u8> {
136//! CORE.view()
137//! }
138//! ```
139//!
140//! You will also need a `hello.udl` file describing the foreign function interface:
141//!
142//! ```ignore
143//! // src/hello.udl
144//! namespace hello {
145//! sequence<u8> process_event([ByRef] sequence<u8> msg);
146//! sequence<u8> handle_response([ByRef] sequence<u8> res);
147//! sequence<u8> view();
148//! };
149//! ```
150//!
151//! Finally, you will need to set up the type generation for the `Model`, `Message` and `ViewModel` types.
152//! See [typegen](https://docs.rs/crux_core/latest/crux_core/typegen/index.html) for details.
153//!
154
155pub mod bridge;
156pub mod capability;
157pub mod command;
158pub mod middleware;
159pub mod testing;
160#[cfg(any(feature = "typegen", feature = "facet_typegen"))]
161pub mod type_generation;
162
163mod capabilities;
164mod core;
165
166pub use capabilities::*;
167pub use command::Command;
168pub use core::{Core, Effect, EffectFFI, Request, RequestHandle, Resolvable, ResolveError};
169#[cfg(feature = "cli")]
170pub use crux_cli as cli;
171#[cfg(feature = "default")]
172pub use crux_macros as macros;
173#[cfg(feature = "typegen")]
174pub use type_generation::serde as typegen;
175
176/// Implement [`App`] on your type to make it into a Crux app. Use your type implementing [`App`]
177/// as the type argument to [`Core`] or [`Bridge`](crate::bridge::Bridge).
178pub trait App {
179 /// `Event`, typically an `enum`, defines the actions that can be taken to update the application state.
180 type Event: Unpin + Send + 'static;
181 /// `Model`, typically a `struct` defines the internal state of the application
182 type Model;
183 /// `ViewModel`, typically a `struct` describes the user interface that should be
184 /// displayed to the user
185 type ViewModel;
186 /// `Effect`, the enum carrying effect requests created by capabilities.
187 /// Normally this type is derived from `Capabilities` using the `crux_macros::Effect` derive macro
188 type Effect: Effect + Unpin;
189
190 /// Update method defines the transition from one `model` state to another in response to an `event`.
191 ///
192 /// `update` may mutate the `model` and returns a [`Command`] describing
193 /// the managed side-effects to perform as a result of the `event`. Commands can be constructed by capabilities
194 /// and combined to run sequentially or concurrently. If migrating from previous version of crux, you
195 /// can return `Command::done()` for compatibility.
196 ///
197 /// For backwards compatibility, `update` may also use the capabilities provided by the `caps` argument
198 /// to instruct the shell to perform side-effects. The side-effects will run concurrently (capability
199 /// calls behave the same as go routines in Go or Promises in JavaScript) with each other and any
200 /// effects captured by the returned `Command`. Capability calls don't return anything, but may
201 /// take a `callback` event which should be dispatched when the effect completes.
202 ///
203 /// Typically, `update` should call at least [`Render::render`](crate::render::Render::render).
204 fn update(
205 &self,
206 event: Self::Event,
207 model: &mut Self::Model,
208 ) -> Command<Self::Effect, Self::Event>;
209
210 /// View method is used by the Shell to request the current state of the user interface
211 fn view(&self, model: &Self::Model) -> Self::ViewModel;
212}