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, orClock. - 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.