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 in a user interface shell, you need to expose the core interface for FFI.
102//! This "plumbing" will likely be simplified with macros in the future versions of Crux.
103//!
104//! ```rust,ignore
105//! // src/lib.rs
106//! pub mod app;
107//!
108//! use lazy_static::lazy_static;
109//! use wasm_bindgen::prelude::wasm_bindgen;
110//!
111//! pub use crux_core::bridge::{Bridge, Request};
112//! pub use crux_core::Core;
113//! pub use crux_http as http;
114//!
115//! pub use app::*;
116//!
117//! uniffi_macros::include_scaffolding!("hello");
118//!
119//! lazy_static! {
120//! static ref CORE: Bridge<Effect, App> = Bridge::new(Core::new::<Capabilities>());
121//! }
122//!
123//! #[wasm_bindgen]
124//! pub fn process_event(data: &[u8]) -> Vec<u8> {
125//! CORE.process_event(data)
126//! }
127//!
128//! #[wasm_bindgen]
129//! pub fn handle_response(id: u32, data: &[u8]) -> Vec<u8> {
130//! CORE.handle_response(id, data)
131//! }
132//!
133//! #[wasm_bindgen]
134//! pub fn view() -> Vec<u8> {
135//! CORE.view()
136//! }
137//! ```
138//!
139//! You will also need a `hello.udl` file describing the foreign function interface:
140//!
141//! ```ignore
142//! // src/hello.udl
143//! namespace hello {
144//! sequence<u8> process_event([ByRef] sequence<u8> msg);
145//! sequence<u8> handle_response([ByRef] sequence<u8> res);
146//! sequence<u8> view();
147//! };
148//! ```
149//!
150//! Finally, you will need to set up the type generation for the `Model`, `Message` and `ViewModel` types.
151//! See [typegen](https://docs.rs/crux_core/latest/crux_core/typegen/index.html) for details.
152//!
153
154pub mod bridge;
155pub mod capability;
156pub mod command;
157pub mod effects;
158pub mod middleware;
159#[cfg(any(test, feature = "testing"))]
160pub mod testing;
161#[cfg(any(feature = "typegen", feature = "facet_typegen"))]
162pub mod type_generation;
163
164#[doc(hidden)]
165#[macro_export]
166#[cfg(any(test, feature = "testing"))]
167macro_rules! __crux_core_testing_items {
168 ($($tokens:tt)*) => {
169 $($tokens)*
170 };
171}
172
173#[doc(hidden)]
174#[macro_export]
175#[cfg(not(any(test, feature = "testing")))]
176macro_rules! __crux_core_testing_items {
177 ($($tokens:tt)*) => {};
178}
179
180mod capabilities;
181mod core;
182
183pub use capabilities::*;
184pub use command::Command;
185pub use core::{Core, Effect, EffectFFI, Request, RequestHandle, Resolvable, ResolveError};
186#[cfg(feature = "uniffi_compat_bindgen")]
187#[deprecated(
188 since = "0.19.0",
189 note = "UniFFI bindgen support is deprecated; use BoltFFI package/generate commands instead"
190)]
191pub mod bindgen;
192#[cfg(feature = "default")]
193pub use crux_macros as macros;
194#[cfg(feature = "typegen")]
195pub use type_generation::serde as typegen;
196
197/// Implement [`App`] on your type to make it into a Crux app. Use your type implementing [`App`]
198/// as the type argument to [`Core`] or [`Bridge`](crate::bridge::Bridge).
199pub trait App {
200 /// `Event`, typically an `enum`, defines the actions that can be taken to update the application state.
201 type Event: Unpin + Send + 'static;
202 /// `Model`, typically a `struct` defines the internal state of the application
203 type Model;
204 /// `ViewModel`, typically a `struct` describes the user interface that should be
205 /// displayed to the user
206 type ViewModel;
207 /// `Effect`, the enum carrying effect requests created by capabilities.
208 /// Normally this type is derived from `Capabilities` using the `crux_macros::Effect` derive macro
209 type Effect: Effect + Unpin;
210
211 /// Update method defines the transition from one `model` state to another in response to an `event`.
212 ///
213 /// `update` may mutate the `model` and returns a [`Command`] describing
214 /// the managed side-effects to perform as a result of the `event`. Commands can be constructed by capabilities
215 /// and combined to run sequentially or concurrently. If migrating from previous version of crux, you
216 /// can return `Command::done()` for compatibility.
217 ///
218 /// For backwards compatibility, `update` may also use the capabilities provided by the `caps` argument
219 /// to instruct the shell to perform side-effects. The side-effects will run concurrently (capability
220 /// calls behave the same as go routines in Go or Promises in JavaScript) with each other and any
221 /// effects captured by the returned `Command`. Capability calls don't return anything, but may
222 /// take a `callback` event which should be dispatched when the effect completes.
223 ///
224 /// Typically, `update` should call at least [`render`](crate::render::render).
225 fn update(
226 &self,
227 event: Self::Event,
228 model: &mut Self::Model,
229 ) -> Command<Self::Effect, Self::Event>;
230
231 /// View method is used by the Shell to request the current state of the user interface
232 fn view(&self, model: &Self::Model) -> Self::ViewModel;
233}