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//! * Capability - A user-friendly API used to request effects and provide events that should be dispatched
38//! when an effect is completed. For example, a HTTP client is a capability.
39//!
40//! * Command - A description of a side-effect to be executed by the shell. Commands can be combined
41//! (synchronously with combinators, or asynchronously with Rust async) to run
42//! sequentially or concurrently, or any combination thereof.
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, Render}, 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//!// Capabilities listing the side effects the Core
66//!// will use to request side effects from the Shell
67//!#[cfg_attr(feature = "typegen", derive(crux_core::macros::Export))]
68//!#[derive(Effect)]
69//!pub struct Capabilities {
70//! pub render: Render<Event>,
71//!}
72//!
73//!#[derive(Default)]
74//!struct Hello;
75//!
76//!impl App for Hello {
77//! // Use the above Event
78//! type Event = Event;
79//! // Use the above Model
80//! type Model = Model;
81//! type ViewModel = String;
82//! // Use the above Capabilities
83//! type Capabilities = Capabilities;
84//! // Use the above generated Effect
85//! type Effect = Effect;
86//!
87//! fn update(&self, event: Event, model: &mut Model, caps: &Capabilities) -> Command<Effect, Event> {
88//! match event {
89//! Event::Increment => model.count += 1,
90//! Event::Decrement => model.count -= 1,
91//! Event::Reset => model.count = 0,
92//! };
93//!
94//! // Request a UI update
95//! render::render()
96//! }
97//!
98//! fn view(&self, model: &Model) -> Self::ViewModel {
99//! format!("Count is: {}", model.count)
100//! }
101//!}
102//! ```
103//!
104//! ## Integrating with a Shell
105//!
106//! To use the application in a user interface shell, you need to expose the core interface for FFI.
107//! This "plumbing" will likely be simplified with macros in the future versions of Crux.
108//!
109//! ```rust,ignore
110//! // src/lib.rs
111//! pub mod app;
112//!
113//! use lazy_static::lazy_static;
114//! use wasm_bindgen::prelude::wasm_bindgen;
115//!
116//! pub use crux_core::bridge::{Bridge, Request};
117//! pub use crux_core::Core;
118//! pub use crux_http as http;
119//!
120//! pub use app::*;
121//!
122//! uniffi_macros::include_scaffolding!("hello");
123//!
124//! lazy_static! {
125//! static ref CORE: Bridge<Effect, App> = Bridge::new(Core::new::<Capabilities>());
126//! }
127//!
128//! #[wasm_bindgen]
129//! pub fn process_event(data: &[u8]) -> Vec<u8> {
130//! CORE.process_event(data)
131//! }
132//!
133//! #[wasm_bindgen]
134//! pub fn handle_response(id: u32, data: &[u8]) -> Vec<u8> {
135//! CORE.handle_response(id, data)
136//! }
137//!
138//! #[wasm_bindgen]
139//! pub fn view() -> Vec<u8> {
140//! CORE.view()
141//! }
142//! ```
143//!
144//! You will also need a `hello.udl` file describing the foreign function interface:
145//!
146//! ```ignore
147//! // src/hello.udl
148//! namespace hello {
149//! sequence<u8> process_event([ByRef] sequence<u8> msg);
150//! sequence<u8> handle_response([ByRef] sequence<u8> res);
151//! sequence<u8> view();
152//! };
153//! ```
154//!
155//! Finally, you will need to set up the type generation for the `Model`, `Message` and `ViewModel` types.
156//! See [typegen](https://docs.rs/crux_core/latest/crux_core/typegen/index.html) for details.
157//!
158
159pub mod bridge;
160pub mod capability;
161pub mod command;
162pub mod testing;
163#[cfg(feature = "typegen")]
164pub mod typegen;
165
166mod capabilities;
167mod core;
168
169use serde::Serialize;
170
171pub use capabilities::*;
172pub use capability::{Capability, WithContext};
173pub use command::Command;
174pub use core::{Core, Effect, Request, ResolveError};
175pub use crux_macros as macros;
176
177/// Implement [`App`] on your type to make it into a Crux app. Use your type implementing [`App`]
178/// as the type argument to [`Core`] or [`Bridge`](crate::bridge::Bridge).
179pub trait App: Default {
180 /// `Event`, typically an `enum`, defines the actions that can be taken to update the application state.
181 type Event: Unpin + Send + 'static;
182 /// `Model`, typically a `struct` defines the internal state of the application
183 type Model: Default;
184 /// `ViewModel`, typically a `struct` describes the user interface that should be
185 /// displayed to the user
186 type ViewModel: Serialize;
187 /// `Capabilities`, usually a `struct`, lists the capabilities used by this application.
188 ///
189 /// Typically, Capabilities should contain at least an instance of the built-in [`Render`](crate::render::Render) capability.
190 ///
191 /// Note: this `Capabilities` associated type will be deprecated soon as part of the completion of
192 /// the migration to the new [`Command`](command) API.
193 type Capabilities;
194 /// `Effect`, the enum carrying effect requests created by capabilities.
195 /// Normally this type is derived from `Capabilities` using the `crux_macros::Effect` derive macro
196 type Effect: Effect + Unpin;
197
198 /// Update method defines the transition from one `model` state to another in response to an `event`.
199 ///
200 /// `update` may mutate the `model` and returns a [`Command`] describing
201 /// the managed side-effects to perform as a result of the `event`. Commands can be constructed by capabilities
202 /// and combined to run sequentially or concurrently. If migrating from previous version of crux, you
203 /// can return `Command::done()` for compatibility.
204 ///
205 /// For backwards compatibility, `update` may also use the capabilities provided by the `caps` argument
206 /// to instruct the shell to perform side-effects. The side-effects will run concurrently (capability
207 /// calls behave the same as go routines in Go or Promises in JavaScript) with each other and any
208 /// effects captured by the returned `Command`. Capability calls don't return anything, but may
209 /// take a `callback` event which should be dispatched when the effect completes.
210 ///
211 /// Typically, `update` should call at least [`Render::render`](crate::render::Render::render).
212 fn update(
213 &self,
214 event: Self::Event,
215 model: &mut Self::Model,
216 caps: &Self::Capabilities,
217 ) -> Command<Self::Effect, Self::Event>;
218
219 /// View method is used by the Shell to request the current state of the user interface
220 fn view(&self, model: &Self::Model) -> Self::ViewModel;
221}
222
223#[cfg(feature = "cli")]
224/// Run the CLI application for codegen and bindgen from your shared library
225/// # Errors
226/// This function may fail if the CLI application encounters an error.
227pub fn run_cli() -> anyhow::Result<()> {
228 crux_cli::run()
229}