# 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>`, 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 | Type | Caller | Actor | Pattern | |------|--------|-------|---------| | `Func` | `call(req) -> resp` | `msg.req`, `msg.reply(resp)` | function call | | `Query` | `call() -> resp` | `msg.reply(resp)` | getter | | `Proc` | `call(req)` | `msg.req`, `msg.done()` | setter / command | | `Signal` | `call()` | `msg.done()` | trigger / reset | | `Inbox` | `send(v)` / `try_send(v)` | `inbox.recv()` | fire-and-forget | | `Lifecycle` | `stop()` | `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 ```rust use pickle::{Signal, Query, Lifecycle, spawn}; use crossbeam_channel::select; struct Counter { inc: Signal, get: Query, 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](https://git.smesh.lol/moxie/blob/docs/MOXIE_ARCHITECTURE_PATTERNS.md) - 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 ```toml [dependencies] pickle = { git = "https://git.smesh.lol/pickle" } ``` ## License AGPL-3.0-or-later