Skip to main content

Crate crux_core

Crate crux_core 

Source
Expand description

Cross-platform app development in Rust

Crux helps you share your app’s business logic and behavior across mobile (iOS and Android) and web, as a single, reusable core built with Rust.

Unlike React Native, the user interface layer is built natively, with modern declarative UI frameworks such as Swift UI, Jetpack Compose and React/Vue or a WASM based framework on the web.

The UI layer is as thin as it can be, and all other work is done by the shared core. The interface with the core has static type checking across languages.

§Getting Started

Crux applications are split into two parts: a Core written in Rust and a Shell written in the platform native language (e.g. Swift or Kotlin). It is also possible to use Crux from Rust shells. The Core architecture is based on Elm architecture.

Quick glossary of terms to help you follow the example:

  • Core - the shared core written in Rust

  • Shell - the native side of the app on each platform handling UI and executing side effects

  • App - the main module of the core containing the application logic, especially model changes and side-effects triggered by events. An App can delegate to child apps, mapping Events and Effects.

  • Event - main input for the core, typically triggered by user interaction in the UI

  • Model - data structure (typically tree-like) holding the entire application state

  • View model - data structure describing the current state of the user interface

  • Effect - A side-effect the core can request from the shell. This is typically a form of I/O or similar interaction with the host platform. Updating the UI is considered an effect.

  • Command - A description of a side-effect or a sequence of side-effects to be executed by the shell. Commands can be combined (synchronously with combinators, or asynchronously with Rust async) to run sequentially or concurrently, or any combination thereof.

  • Capability - A user-friendly API used to create Commands for a specific effect type (e.g. HTTP)

Below is a minimal example of a Crux-based application Core:

// src/app.rs
use crux_core::{render::{self, RenderOperation}, App, macros::effect, Command};
use serde::{Deserialize, Serialize};

// Model describing the application state
#[derive(Default)]
struct Model {
   count: isize,
}

// Event describing the actions that can be taken
#[derive(Serialize, Deserialize)]
pub enum Event {
   Increment,
   Decrement,
   Reset,
}

// Effects the Core will request from the Shell
#[effect(typegen)]
pub enum Effect {
   Render(RenderOperation),
}

#[derive(Default)]
struct Hello;

impl App for Hello {
   // Use the above Event
   type Event = Event;
   // Use the above Model
   type Model = Model;
   type ViewModel = String;
   // Use the above generated Effect
   type Effect = Effect;

   fn update(&self, event: Event, model: &mut Model) -> Command<Effect, Event> {
       match event {
           Event::Increment => model.count += 1,
           Event::Decrement => model.count -= 1,
           Event::Reset => model.count = 0,
       };

       // Request a UI update
       render::render()
   }

   fn view(&self, model: &Model) -> Self::ViewModel {
       format!("Count is: {}", model.count)
   }
}

§Integrating with a Shell

To use the application from a shell, wrap the Core in a Bridge, which presents the same interface in serialized form, so that events, effect requests and the view model can cross the FFI boundary as bytes.

// src/ffi.rs
use crux_core::{
    Core,
    bridge::{Bridge, EffectId},
};

pub struct CoreFfi {
    core: Bridge<Hello>,
}

impl CoreFfi {
    pub fn new() -> Self {
        Self {
            core: Bridge::new(Core::new()),
        }
    }

    /// Send an event to the app, returning the serialized effect requests it caused.
    pub fn update(&self, event: &[u8]) -> Vec<u8> {
        let mut requests = vec![];
        self.core
            .update(event, &mut requests)
            .expect("event should deserialize");

        requests
    }

    /// Resolve an effect request with the shell's output, returning any follow-up requests.
    pub fn resolve(&self, id: u32, output: &[u8]) -> Vec<u8> {
        let mut requests = vec![];
        self.core
            .resolve(EffectId(id), output, &mut requests)
            .expect("output should deserialize");

        requests
    }

    /// Get the current view model, serialized.
    pub fn view(&self) -> Vec<u8> {
        let mut view = vec![];
        self.core.view(&mut view).expect("view model should serialize");

        view
    }
}

The three methods above are the entire interface the shell sees. In a real app you would handle the errors rather than panicking on them.

The bindings which let Swift, Kotlin, TypeScript or C# call those methods are generated by BoltFFI. Annotate the impl block with #[boltffi::export], describe your targets in a boltffi.toml, and run boltffi pack apple (or android, wasm) to build the library and generate the foreign code that calls it:

#[boltffi::export]
impl CoreFfi {
    // ...as above
}

§Type generation

The shell also needs its own definitions of the types crossing the boundary — Event, ViewModel and the effect payloads. These are generated separately from the FFI bindings, by deriving Facet on those types and running a codegen binary against them, behind the facet_typegen feature. See type_generation::facet for details.

The counter example shows all of this end to end, with shells written in Swift, Kotlin, TypeScript, C# and Rust.

Re-exports§

pub use command::Command;
pub use type_generation::serde as typegen;
pub use crux_macros as macros;

Modules§

bridge
capability
command
Command represents one or more side-effects, resulting in interactions with the shell.
effects
Support for routing effects to explicit, type-based handlers.
middleware
Middleware which can be wrapped around the Core to modify its behaviour.
render
Built-in capability used to notify the Shell that a UI update is necessary.
type_generation

Structs§

Core
The Crux core. Create an instance of this type with your App type as the type parameter
Request
Request represents an effect request from the core to the shell.

Enums§

RequestHandle
Resolve is a callback used to resolve an effect request and continue one of the capability Tasks running on the executor.
ResolveError

Traits§

App
Implement App on your type to make it into a Crux app. Use your type implementing App as the type argument to Core or Bridge.
Effect
Implemented automatically with the effect macro from crux_macros. This is a marker trait to ensure the macro generated traits are present on the effect type.
EffectFFI
Implemented automatically with the effect macro from crux_macros.
Resolvable