The Principal Dev โ€“ Masterclass for Tech Leads

The Principal Dev โ€“ Masterclass for Tech LeadsJuly 17-18

Join

Crux ยท GitHub license Crate version Docs Build status

Watch the introductory talk | Read the book | Read API docs | Join Zulip community

Cross-platform app development in Rust

Getting Started

Learn how to use Crux in your project.

Read the API documentation

Watch the introductory talk at the recent Rust Nation 2023 conference in London.

You can also join the friendly conversation on our Zulip channel.

[!NOTE] Crux is pre 1.0 and under active development. It is production-ready, but occasional breaking changes to the API can be expected. We do our best to limit the extent of these and provide a smooth, gradual migration path

Architectural Overview

Logical architecture

Crux has managed side-effects, it strictly separates pure computational tasks from tasks that cause side effects. This is similar to the way Elm works.

Side-effect-free core

In the above diagram, the inner "Core" is compiled and linked to the outer "Shell" on each platform as a library:

In fact, because WebAssembly (Wasm) is one of the compilation targets, the core must remain side-effect free, due to the sandboxed nature of the Wasm runtime environment.

As such, the core is completely isolated and secure against software supply-chain attacks, as it has no access to any external APIs. All it can do is perform pure calculations and keep internal state.

Following the Elm architecture, the core defines the key component types within the application:

The first three are tied together by the update function, familiar from Elm, Redux or other event sourcing architectures, which currently has this type signature:

fn update(
    &self,
    msg: Event,
    model: &mut Model,
    _caps: &Capabilities, // soon to be deprecated
) -> Command<Effect, Event> {
    // ...
}

The job of the update function is to process an Event, update the model accordingly, and potentially request some side-effects.

[!NOTE] The Capability API is being deprecated in favour of a more flexible Command API.

To learn more about the new Command API, see the page in the book that describes Managed Effects, or look at examples/counter.

Application Shell

The enclosing platform native "Shell" is written using the language appropriate for the platform, and acts as the runtime environment within which all the non-pure tasks are performed. From the perspective of the core, the shell is the platform on which the core runs.

Testing

Tests can act as another Shell, exercising the Core in the same way a real app would, observing and resolving the requested effects and checking the model and view model are correct. No need for fakes, mocks or stubs.

Communication Between the Application Shell and the Core

Following the Elm architecture, the interface with the core is message based. To perform any task that creates a side-effect (such as an HTTP call or random number generation), the core must request it from the shell as an Effect. Effects support fire-and-forget, request/response, and streaming semantics.

Crux has a concept of Capabilities โ€” reusable interfaces for common side-effects which can be used in the Core as a more ergonomic API.

The only built-in capability is Render. But this repository contains a few capabilities at various stages of maturity, and you can easily write your own if you want to:

crux

Published capabilities

  1. Render (ask UI to render the ViewModel) โ€” source, built-in to crux_core, request only
  2. Http (full HTTP implementation based on the Surf API) โ€” source, crate, request/response
  3. KeyValue (basic key-value store API) โ€” source, crate, request/response
  4. Time (get current time, notify after duration, notify at instant) โ€” source, crate, request/response
  5. Platform (get the current platform) โ€” source, crate, request/response

Example custom capabilities

  1. SSE (basic Server-Sent Events) โ€” source, request/streaming
  2. PubSub (pub sub with streaming) โ€” source, request/response/streaming
  3. Delay โ€” part of tutorial in the book

Foreign Function Interface with type generation

The core API interface is very minimal:

The Foreign Function Interface allowing the shell to call the above functions is provided by Mozilla's UniFFI on a mobile device, or in the browser, by wasm-pack.

In order to both send more complex data than UniFFI currently supports, and enforce the message passing semantics, all messages are serialized, sent across the boundary, then deserialized using serde-generate which also provides type generation for the foreign (non-Rust) languages.

This means that changes to types in the core, especially the Event and Request types, propagate out into the shell implementations and cause type errors where appropriate (such as an exhaustive match on an enum check).

Message Types

Three types of message are exchanged between the application and the core.

Request messages contain the inputs for the requested side-effect, along with a id used by the core to pair requests and their responses together. The exact mechanics are not important, but it is important for the request's id to be passed on to the corresponding response.

Example Message Exchange Cycle

A typical message exchange cycle may look like this:

  1. User interaction occurs in the Shell, which results in an event
  2. The Shell handles this event by constructing an Event
  3. The Shell calls the Core's process_event function passing the Event as an argument
  4. The Core performs the required processing, updating both its inner state and the view model
  5. The Core returns one or more Request messages to the Shell (inside an enum tagging the type of request)

In the simplest case, the Core will respond to an Event by returning the single Request - render.

This requests that the Shell re-renders the user interface. When Render is the only response from the Core, the message cycle has completed and the Core has now "settled".

In more complex cases however, the Core may well return multiple Requests; each of which instructs the Shell to perform a side-effect-inducing task such as:

Many of these side-effecting-inducing tasks are asynchronous. The Shell is responsible for passing responses back to the core (to the handle_response function), which may respond with further requests.

This exchange continues until the core stops requesting further side-effects (typically the last side-effect requested would again be Render).


Sponsors

Crux is kindly sponsored by the following organizations. Your help is very much appreciated.


Red Badger Consulting Limited

Red Badger logo

Red Badger is the digital product consultancy trusted by blue chips and global brands. Our product design and technical pedigree allow us to craft high-impact digital products customers want. We use modern engineering approaches to deliver sustainable change. And embed digital capabilities to power continuous innovation.


Zulip

Zulip round icon

Zulip is an open-source modern team chat app designed to keep both live and asynchronous conversations organized.

Zulip sponsor Crux by providing our Zulip server โ€” thank you Zulip!


Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.