Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

krucyfiks is a small toolkit for writing Rust applications where the core application stays ordinary Rust.

The app owns its model. It receives events. It updates state. It exposes a view model. When it needs the outside world, it calls async traits:

#![allow(unused)]
fn main() {
#[krucyfiks::effect]
#[async_trait::async_trait]
pub trait Random: Send + Sync {
    async fn get_number(&self) -> i64;
}

#[krucyfiks::effect]
#[async_trait::async_trait]
pub trait Render: Send + Sync {
    async fn render(&self);
}
}

That is the main rule. Application code should describe capabilities it needs, not the transport that will provide them.

The same app can then run in different environments:

  • a unit test can receive effects through channels and answer them manually;
  • a Ratatui runtime can implement one direct effect handler;
  • a UniFFI boundary can expose plain data enums to foreign code;
  • a debugger can log or replay protocol values.

Those are adapters around the app. They are not the app architecture itself.

Mental Model

There are two layers:

  • Capabilities are small async traits such as Random, Render, Logger, Http, or Clock.
  • Protocols are plain enums that represent effect requests and outputs at a boundary.

Most application code should only see capabilities. Boundary code can choose the protocol shape that is useful for tests, tracing, FFI, or UI integration.

The macros exist to remove glue:

  • #[krucyfiks::effect] turns a capability trait into request/output enums and adapters;
  • #[derive(krucyfiks::Effect)] composes smaller effect enums into a larger protocol enum.

The result is intentionally flexible. You can write a simple app without a runtime, wrap it in The Elm Architecture when you want inspectable effects, or export it through UniFFI without changing the domain model.