pickle

helpers for descaling your rust - Actor CSP concurrency with isolation guarantees and no need to borrow or annotate lifetimes

git clone https://git.smesh.lol/pickle.git

pickle

pickle your rust to make it into shiny chrome

CSP/Actor model architecture for Rust applications.

Generic building blocks for actor-model concurrency. Each type maps to a common inter-thread communication pattern, eliminating the per-actor boilerplate of defining message enums, oneshot channels, and caller wrappers.

The big win: actors own their state inside a spawned thread. Communication is by value through channels - moved, not borrowed. Callers hold typed channel handles that are Clone + Send. No Arc<Mutex<T>>, no lifetime annotations, no &self vs &mut self disputes. Rust's ownership system does exactly what you want when every interaction is an ownership transfer.

Types

TypeCallerActorPattern
Func<Req, Resp>call(req) -> respmsg.req, msg.reply(resp)function call
Query<Resp>call() -> respmsg.reply(resp)getter
Proc<Req>call(req)msg.req, msg.done()setter / command
Signalcall()msg.done()trigger / reset
Inbox<T>send(v) / try_send(v)inbox.recv()fire-and-forget
Lifecyclestop()stopping()shutdown

All synchronous types use rendezvous channels (zero-capacity). The caller blocks until the actor processes the message. This eliminates races where a buffered write appears to succeed but the actor hasn't seen it yet.

Example

use pickle::{Signal, Query, Lifecycle, spawn};
use crossbeam_channel::select;

struct Counter {
    inc: Signal,
    get: Query<i32>,
    lc: Lifecycle,
}

impl Counter {
    fn new() -> Self {
        let mut c = Counter {
            inc: Signal::new(),
            get: Query::new(),
            lc: Lifecycle::new(),
        };
        let inc = c.inc.receiver();
        let get = c.get.receiver();
        let stopping = c.lc.stopping();
        spawn(&mut c.lc, move || {
            let mut n: i32 = 0;
            loop {
                select! {
                    recv(stopping) -> _ => return,
                    recv(inc) -> msg => {
                        let msg = msg.unwrap();
                        n += 1;
                        msg.done();
                    },
                    recv(get) -> msg => {
                        let msg = msg.unwrap();
                        msg.reply(n);
                    },
                }
            }
        });
        c
    }

    fn inc(&self) { self.inc.call(); }
    fn get(&self) -> i32 { self.get.call() }
}

Design Principles

  • No sync primitives. No mutexes, no atomics, no RwLock. All

coordination is via channels and select.

  • No borrowing. Actor state lives inside a thread closure. Callers

interact through owned channel handles. Lifetimes don't appear.

  • Rendezvous by default. Synchronous types guarantee

happens-before: when call() returns, the actor has processed the message. Use Inbox for intentionally async paths.

  • State ownership. The actor thread owns all mutable state.

Callers get copies via response channels, never references.

  • Composable. Use the channel types you need. No trait to

implement, no framework, no registration.

When to use what

  • Func - caller needs a computed result based on arguments (map

lookup, filtered query)

  • Query - caller needs current state, no arguments (get count, get

config)

  • Proc - caller needs to mutate state and wait for confirmation

(set value, record event)

  • Signal - caller needs to trigger an action and wait (reset,

flush, increment)

  • Inbox - caller must not block (logging, metrics, notifications).

Use try_send to drop on backpressure.

Architecture Guide

This library implements the intra-domain actor patterns described in Moxie Architecture Patterns - specifically the actor loop (section 3.1), typed operation channels (section 3.2), the synchronous-mutation rule (section 3.3), and context-aware calls (section 3.6). The five channel types are a direct realization of the taxonomy from section 3.2.

Family

Same architecture, three languages:

  • [actor](https://git.smesh.lol/actor) (Go) - go get git.smesh.lol/actor
  • pickle (Rust) - pickle = { git = "https://git.smesh.lol/pickle" }
  • [anneal](https://git.smesh.lol/anneal) (TypeScript) - npm install anneal

Install

[dependencies]
pickle = { git = "https://git.smesh.lol/pickle" }

License

AGPL-3.0-or-later

files