Skip to main content

byteflow/
lib.rs

1//! Byteflow — embeddable **flow** runtime (package **`byteflow-actors`**).
2//!
3//! Not a language, not Tokio, not a JVM. You assemble register bytecode in
4//! host Rust ([`ChunkBuilder`]), spawn many lightweight **flows** on an M:N
5//! scheduler, and they talk through mailboxes with a strict hop protocol.
6//!
7//! Dependents write `use byteflow::...` (crate name) while crates.io lists
8//! the package as [`byteflow-actors`](https://crates.io/crates/byteflow-actors).
9//!
10//! # What you get
11//!
12//! | Piece | Role |
13//! |-------|------|
14//! | [`ChunkBuilder`] / [`Opcode`] | Assemble `.bf` programs in Rust (no source language) |
15//! | [`Vm`] / [`VmResult`] | Per-flow register interpreter; effects hand off to the scheduler |
16//! | [`Runtime`] | Worker pool + timer; spawn / join / host [`Runtime::send`] |
17//! | [`Value::Message`] | **Atomic Hop** envelope — the only value allowed on `Send` / `Ask` |
18//! | [`Value::Cap`] | **FlowCap** address for bytecode delivery (`Send` / `Ask` targets) |
19//! | [`Supervisor`] | Restart policies when a flow fails |
20//! | [`std_native_table`] | `print`, `now_ms`, `make_msg`, `msg_*`, `msg_reply_cap` |
21//!
22//! # Atomic Hop (messaging contract)
23//!
24//! Every bytecode `Send` / `Ask` carries exactly one [`Message`]:
25//!
26//! ```text
27//! Message { sender, reply_cap, request_id, tag, payload }
28//! ```
29//!
30//! - Bare `Int` / `Pid` / `Str` on `Send` → trap / [`SendError::NotAHop`]
31//! - Scheduler **stamps** `sender` (authenticated origin) and mints
32//!   `reply_cap` (SEND-only Cap back to the caller)
33//! - Reply with [`std_native_table`]'s `msg_reply_cap` — **not** `msg_sender`
34//!   (`Pid` is identity, not an address)
35//!
36//! Also: selective receive (`ReceiveMatch`), and `Ask` for correlated RPC.
37//!
38//! # FlowCap (addressing)
39//!
40//! | Value | Use |
41//! |-------|-----|
42//! | [`Value::Cap`] | Target of `Send` / `Ask`; from `SelfPid`, `Spawn`, or `reply_cap` |
43//! | [`Value::Pid`] | Identity inside a delivered hop (`msg_sender`) |
44//!
45//! Host [`Runtime::send`] still takes [`FlowId`] (trusted embedder path).
46//!
47//! # Values (ABI v4)
48//!
49//! `Unit | Bool | Int | Float | Pid | Message | Cap | Str | Bytes`
50//!
51//! `Str` / `Bytes` are `Arc`-backed for cheap register/mailbox clones. They
52//! are **not** Atomic Hops by themselves.
53//!
54//! # Quick start — scalar
55//!
56//! ```
57//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
58//! use byteflow::{ChunkBuilder, Opcode, FlowOutcome, Runtime, Value};
59//!
60//! let mut b = ChunkBuilder::new("demo");
61//! b.begin_function("main", 0, 2);
62//! b.emit_load_imm(0, 41);
63//! b.emit_load_imm(1, 1);
64//! b.emit_binop(Opcode::Add, 0, 0, 1);
65//! b.emit_return(0);
66//!
67//! let rt = Runtime::new(b.finish())?;
68//! let outcome = rt.spawn(0, &[])?.join();
69//! rt.shutdown();
70//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
71//! # Ok(())
72//! # }
73//! ```
74//!
75//! # Quick start — Atomic Hop (ping-pong)
76//!
77//! Hop demos need the std native table (`make_msg` / `msg_*`):
78//!
79//! ```
80//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
81//! use byteflow::{samples, std_native_table, FlowOutcome, Runtime, Value};
82//!
83//! let rt = Runtime::with_natives(samples::ping_pong(), std_native_table())?;
84//! let main = rt.function_index("main").expect("main");
85//! let outcome = rt.spawn(main, &[])?.join();
86//! rt.shutdown();
87//! assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
88//! # Ok(())
89//! # }
90//! ```
91//!
92//! More samples: [`samples::atomic_request_reply`], [`samples::ask_reply`],
93//! [`samples::selective_receive`], forged-sender security regressions.
94//!
95//! # Design guides (rendered on docs.rs)
96//!
97//! - [`docs::atomic_hop`] — hop protocol, Cap addressing, natives table
98//! - [`docs::security`] — threat model, invariants S1–S7, roadmap
99//! - [`docs::error_model`] — fail-closed errors (no production `unwrap`)
100//!
101//! # What this is *not*
102//!
103//! - Not a replacement for Tokio / async Rust (no `.await` IO loop)
104//! - Not a distributed cluster runtime (single process, in-memory mailboxes)
105//! - Not a full object-capability OS (native quotas / Cap attenuation come later)
106//!
107//! Host owns I/O and policy. Byteflow owns cheap concurrency and hop delivery.
108#![forbid(unsafe_code)]
109// Tests may use unwrap/expect for brevity; production paths must not.
110#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
111#![cfg_attr(docsrs, feature(doc_cfg))]
112
113pub mod bytecode;
114pub mod log;
115pub mod natives;
116pub mod samples;
117pub mod scheduler;
118pub mod vm;
119
120/// Long-form design notes shipped inside the crate (also under `docs/` on GitHub).
121///
122/// These modules exist so [docs.rs](https://docs.rs/byteflow-actors) shows the
123/// same guides as the repository, not only API rustdoc.
124pub mod docs {
125    /// Atomic Hop: Message-only Send, FlowCap addressing, Ask, selective receive.
126    #[doc = include_str!("../docs/atomic-hop.md")]
127    pub mod atomic_hop {}
128
129    /// Security model: authenticated sender, FlowCap, invariants S1–S7.
130    #[doc = include_str!("../docs/security.md")]
131    pub mod security {}
132
133    /// Fail-closed error taxonomy and mutex policy.
134    #[doc = include_str!("../docs/error-model.md")]
135    pub mod error_model {}
136}
137
138pub use bytecode::{
139    asm_macros, decode, disassemble, encode, verify, Chunk, ChunkBuilder, FormatError,
140    FunctionDef, Instruction, Label, Message, Opcode, Value, VerifyError, ABI_VERSION, MAGIC,
141};
142pub use natives::{std_native_map, std_native_table, std_natives};
143pub use scheduler::{
144    fault_count, next_flow_id, flow_id_from_u64, report_fault, CapId, CapRights, ChildSpec,
145    Delivery, Mailbox, Flow, FlowHandle, FlowId, FlowMetrics, FlowOutcome, FlowState,
146    RestartPolicy, Runtime, RuntimeConfig, RuntimeError, RuntimeMetrics,
147    RuntimeMetricsSnapshot, RuntimeSpawner, SendError, SpawnError, Supervisor,
148    SupervisorConfig, DEFAULT_QUANTUM,
149};
150pub use vm::{
151    expect_arg, expect_bool, expect_int, expect_message, expect_u64, Fault, NativeFn,
152    NativeResult, NativeTable, NativeTableBuilder, Vm, VmResult, MAX_CALL_DEPTH,
153};
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn std_native_map_has_stable_indices() {
161        let map = std_native_map();
162        assert_eq!(map["print"], 0);
163        assert_eq!(map["now_ms"], 1);
164        assert_eq!(map["make_msg"], 2);
165        assert_eq!(map["msg_payload"], 6);
166        assert_eq!(map["msg_reply_cap"], 7);
167    }
168
169    #[test]
170    fn chunk_builder_with_std_natives() {
171        let mut b = ChunkBuilder::new("std-natives-demo");
172        b.begin_function("main", 0, 2);
173        b.emit_load_imm(0, 42);
174        b.emit_call_native(0, 0, 1);
175        b.emit_call_native(1, 1, 0);
176        b.emit_return(1);
177
178        let chunk = b.finish();
179        verify(&chunk).expect("verify");
180
181        let rt = Runtime::with_natives(chunk, std_native_table()).expect("runtime");
182        let outcome = rt.spawn(0, &[]).expect("spawn").join();
183        rt.shutdown();
184
185        match outcome {
186            FlowOutcome::Completed(Value::Int(ms)) => assert!(ms >= 0),
187            other => panic!("expected Completed(Value::Int(_)), got {other:?}"),
188        }
189    }
190}