Skip to main content

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}