crux_core/lib.rs
1//! Cross-platform app development in Rust
2//!
3//! Crux helps you share your app's business logic and behavior across mobile (iOS and Android) and web,
4//! as a single, reusable core built with Rust.
5//!
6//! Unlike React Native, the user interface layer is built natively, with modern declarative UI frameworks
7//! such as Swift UI, Jetpack Compose and React/Vue or a WASM based framework on the web.
8//!
9//! The UI layer is as thin as it can be, and all other work is done by the shared core.
10//! The interface with the core has static type checking across languages.
11//!
12//! ## Getting Started
13//!
14//! Crux applications are split into two parts: a Core written in Rust and a Shell written in the platform
15//! native language (e.g. Swift or Kotlin). It is also possible to use Crux from Rust shells.
16//! The Core architecture is based on [Elm architecture](https://guide.elm-lang.org/architecture/).
17//!
18//! Quick glossary of terms to help you follow the example:
19//!
20//! * Core - the shared core written in Rust
21//!
22//! * Shell - the native side of the app on each platform handling UI and executing side effects
23//!
24//! * App - the main module of the core containing the application logic, especially model changes
25//! and side-effects triggered by events. An App can delegate to child apps, mapping Events and Effects.
26//!
27//! * Event - main input for the core, typically triggered by user interaction in the UI
28//!
29//! * Model - data structure (typically tree-like) holding the entire application state
30//!
31//! * View model - data structure describing the current state of the user interface
32//!
33//! * Effect - A side-effect the core can request from the shell. This is typically a form of I/O or similar
34//! interaction with the host platform. Updating the UI is considered an effect.
35//!
36//! * Command - A description of a side-effect or a sequence of side-effects to be executed by the shell.
37//! Commands can be combined (synchronously with combinators, or asynchronously with Rust async) to run
38//! sequentially or concurrently, or any combination thereof.
39//!
40//! * Capability - A user-friendly API used to create Commands for a specific effect type (e.g. HTTP)
41//!
42//!
43//! Below is a minimal example of a Crux-based application Core:
44//!
45//! ```rust
46//!// src/app.rs
47//!use crux_core::{render::{self, RenderOperation}, App, macros::effect, Command};
48//!use serde::{Deserialize, Serialize};
49//!
50//!// Model describing the application state
51//!#[derive(Default)]
52//!struct Model {
53//! count: isize,
54//!}
55//!
56//!// Event describing the actions that can be taken
57//!#[derive(Serialize, Deserialize)]
58//!pub enum Event {
59//! Increment,
60//! Decrement,
61//! Reset,
62//!}
63//!
64//!// Effects the Core will request from the Shell
65//!#[effect(typegen)]
66//!pub enum Effect {
67//! Render(RenderOperation),
68//!}
69//!
70//!#[derive(Default)]
71//!struct Hello;
72//!
73//!impl App for Hello {
74//! // Use the above Event
75//! type Event = Event;
76//! // Use the above Model
77//! type Model = Model;
78//! type ViewModel = String;
79//! // Use the above generated Effect
80//! type Effect = Effect;
81//!
82//! fn update(&self, event: Event, model: &mut Model) -> Command<Effect, Event> {
83//! match event {
84//! Event::Increment => model.count += 1,
85//! Event::Decrement => model.count -= 1,
86//! Event::Reset => model.count = 0,
87//! };
88//!
89//! // Request a UI update
90//! render::render()
91//! }
92//!
93//! fn view(&self, model: &Model) -> Self::ViewModel {
94//! format!("Count is: {}", model.count)
95//! }
96//!}
97//! ```
98//!
99//! ## Integrating with a Shell
100//!
101//! To use the application from a shell, wrap the [`Core`] in a [`Bridge`](crate::bridge::Bridge),
102//! which presents the same interface in serialized form, so that events, effect requests and the
103//! view model can cross the FFI boundary as bytes.
104//!
105//! ```rust
106//! # use crux_core::{render::{self, RenderOperation}, App, macros::effect, Command};
107//! # use serde::{Deserialize, Serialize};
108//! # #[derive(Default)]
109//! # struct Model {
110//! # count: isize,
111//! # }
112//! # #[derive(Serialize, Deserialize)]
113//! # pub enum Event {
114//! # Increment,
115//! # }
116//! # #[effect(typegen)]
117//! # pub enum Effect {
118//! # Render(RenderOperation),
119//! # }
120//! # #[derive(Default)]
121//! # struct Hello;
122//! # impl App for Hello {
123//! # type Event = Event;
124//! # type Model = Model;
125//! # type ViewModel = String;
126//! # type Effect = Effect;
127//! # fn update(&self, event: Event, model: &mut Model) -> Command<Effect, Event> {
128//! # match event {
129//! # Event::Increment => model.count += 1,
130//! # };
131//! # render::render()
132//! # }
133//! # fn view(&self, model: &Model) -> Self::ViewModel {
134//! # format!("Count is: {}", model.count)
135//! # }
136//! # }
137//! // src/ffi.rs
138//! use crux_core::{
139//! Core,
140//! bridge::{Bridge, EffectId},
141//! };
142//!
143//! pub struct CoreFfi {
144//! core: Bridge<Hello>,
145//! }
146//!
147//! impl CoreFfi {
148//! pub fn new() -> Self {
149//! Self {
150//! core: Bridge::new(Core::new()),
151//! }
152//! }
153//!
154//! /// Send an event to the app, returning the serialized effect requests it caused.
155//! pub fn update(&self, event: &[u8]) -> Vec<u8> {
156//! let mut requests = vec![];
157//! self.core
158//! .update(event, &mut requests)
159//! .expect("event should deserialize");
160//!
161//! requests
162//! }
163//!
164//! /// Resolve an effect request with the shell's output, returning any follow-up requests.
165//! pub fn resolve(&self, id: u32, output: &[u8]) -> Vec<u8> {
166//! let mut requests = vec![];
167//! self.core
168//! .resolve(EffectId(id), output, &mut requests)
169//! .expect("output should deserialize");
170//!
171//! requests
172//! }
173//!
174//! /// Get the current view model, serialized.
175//! pub fn view(&self) -> Vec<u8> {
176//! let mut view = vec![];
177//! self.core.view(&mut view).expect("view model should serialize");
178//!
179//! view
180//! }
181//! }
182//! ```
183//!
184//! The three methods above are the entire interface the shell sees. In a real app you would
185//! handle the errors rather than panicking on them.
186//!
187//! The bindings which let Swift, Kotlin, TypeScript or C# call those methods are generated by
188//! [BoltFFI](https://www.boltffi.dev/). Annotate the `impl` block with `#[boltffi::export]`,
189//! describe your targets in a `boltffi.toml`, and run `boltffi pack apple` (or `android`, `wasm`)
190//! to build the library and generate the foreign code that calls it:
191//!
192//! ```rust,ignore
193//! #[boltffi::export]
194//! impl CoreFfi {
195//! // ...as above
196//! }
197//! ```
198//!
199//! ## Type generation
200//!
201//! The shell also needs its own definitions of the types crossing the boundary — `Event`,
202//! `ViewModel` and the effect payloads. These are generated separately from the FFI bindings,
203//! by deriving [`Facet`](https://docs.rs/facet) on those types and running a `codegen` binary
204//! against them, behind the `facet_typegen` feature. See
205//! [`type_generation::facet`](https://docs.rs/crux_core/latest/crux_core/type_generation/facet/index.html)
206//! for details.
207//!
208//! The [`counter` example](https://github.com/redbadger/crux/tree/master/examples/counter) shows
209//! all of this end to end, with shells written in Swift, Kotlin, TypeScript, C# and Rust.
210//!
211
212pub mod bridge;
213pub mod capability;
214pub mod command;
215pub mod effects;
216pub mod middleware;
217#[cfg(any(test, feature = "testing"))]
218pub mod testing;
219#[cfg(any(feature = "typegen", feature = "facet_typegen"))]
220pub mod type_generation;
221
222#[doc(hidden)]
223#[macro_export]
224#[cfg(any(test, feature = "testing"))]
225macro_rules! __crux_core_testing_items {
226 ($($tokens:tt)*) => {
227 $($tokens)*
228 };
229}
230
231#[doc(hidden)]
232#[macro_export]
233#[cfg(not(any(test, feature = "testing")))]
234macro_rules! __crux_core_testing_items {
235 ($($tokens:tt)*) => {};
236}
237
238mod capabilities;
239mod core;
240
241pub use capabilities::*;
242pub use command::Command;
243pub use core::{Core, Effect, EffectFFI, Request, RequestHandle, Resolvable, ResolveError};
244#[cfg(feature = "uniffi_compat_bindgen")]
245#[deprecated(
246 since = "0.19.0",
247 note = "UniFFI bindgen support is deprecated; use BoltFFI package/generate commands instead"
248)]
249pub mod bindgen;
250#[cfg(feature = "default")]
251pub use crux_macros as macros;
252#[cfg(feature = "typegen")]
253pub use type_generation::serde as typegen;
254
255/// Implement [`App`] on your type to make it into a Crux app. Use your type implementing [`App`]
256/// as the type argument to [`Core`] or [`Bridge`](crate::bridge::Bridge).
257pub trait App {
258 /// `Event`, typically an `enum`, defines the actions that can be taken to update the application state.
259 type Event: Unpin + Send + 'static;
260 /// `Model`, typically a `struct` defines the internal state of the application
261 type Model;
262 /// `ViewModel`, typically a `struct` describes the user interface that should be
263 /// displayed to the user
264 type ViewModel;
265 /// `Effect`, the enum carrying the effect requests the app can make of the shell.
266 /// Normally this type is written with the `crux_macros::effect` attribute macro,
267 /// which implements the necessary traits for you.
268 type Effect: Effect + Unpin;
269
270 /// Update method defines the transition from one `model` state to another in response to an `event`.
271 ///
272 /// `update` may mutate the `model` and returns a [`Command`] describing
273 /// the managed side-effects to perform as a result of the `event`. Commands are constructed by
274 /// capabilities, and combined to run sequentially or concurrently. If an event requires no
275 /// side-effects, return [`Command::done`].
276 ///
277 /// Typically, `update` should call at least [`render`](crate::render::render).
278 fn update(
279 &self,
280 event: Self::Event,
281 model: &mut Self::Model,
282 ) -> Command<Self::Effect, Self::Event>;
283
284 /// View method is used by the Shell to request the current state of the user interface
285 fn view(&self, model: &Self::Model) -> Self::ViewModel;
286}