一键导入
banish
Reference for writing Rust code using the banish state machine framework. Use when writing, reviewing, or debugging banish! blocks.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Reference for writing Rust code using the banish state machine framework. Use when writing, reviewing, or debugging banish! blocks.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | banish |
| description | Reference for writing Rust code using the banish state machine framework. Use when writing, reviewing, or debugging banish! blocks. |
| user-invocable | false |
Banish is a declarative Rust library for rule-based state machines. You declare states and rules; the framework generates the fixed-point scheduler, interaction tracking, and state advancement at compile time.
[dependencies]
banish = "1.4.1"
use banish::banish;
banish! {
// Block attributes (optional, must come first)
#![async]
#![id = "my_machine"]
#![dispatch(entry_state)]
#![trace]
// Block-level variables (live entire machine lifetime)
let mut count: u32 = 0;
// State declaration
#[max_entry = 3 => @bail]
@my_state
// State-level variables (reset on every entry)
let mut idx: usize = 0;
// Conditional rule
name ? condition { body }
// Conditional rule with fallback (fallback does NOT re-evaluate)
name ? condition { body } !? { else_body }
// Pattern condition fires when the pattern matches (if let semantics)
name ? let Pat = expr { body }
name ? let Pat = expr { body } !? { else_body }
// Conditionless rule (fires exactly once per entry, on first pass)
name? { body }
// Guarded transition (inside a rule body)
=> @other_state if condition;
// Unconditional transition (inside a rule body)
=> @other_state;
}
Within each state, rules evaluate top to bottom repeatedly until a full pass fires nothing (fixed point). Then the scheduler advances to the next non-isolated state.
enter state
└─ evaluate rules top-to-bottom
├─ any rule fired? → re-evaluate from top
└─ no rules fired → fixed point → advance to next state
true. Firing sets __interaction = true.!? fallback branches do not set __interaction — they never cause re-evaluation.name? { }) fire once per entry (first pass only).name ? let Pat = expr { }) use if let semantics. The rule fires when the pattern matches, binding variables into the body.| Scope | Where declared | Lifetime | Resets on re-entry? |
|---|---|---|---|
| Block-level | Before first @state | Entire machine | No |
| State-level | After @state, before first rule | One state entry | Yes — every entry |
// Implicit — scheduler advances to next non-isolated state after fixed point
// (no code needed)
// Explicit — jump immediately, bypasses scheduler
=> @target_state;
// Guarded — conditional jump; execution continues in rule body if false
=> @target_state if some_condition;
Transitions inside a rule body act immediately. They do not need to be the last statement. Code after a taken guarded transition is unreachable.
return expr; exits the entire banish! block with a value. The block is an
expression and can be assigned or returned from a function.
let result: String = banish! {
@work
done ? finished { return compute_result(); }
};
break exits the current state and lets the scheduler advance normally.
continue restarts rule evaluation from the top of the current state immediately.
#[isolate] // removed from scheduler; explicit transition only
#[max_iter = N] // cap fixed-point loop; advance normally on exhaustion
#[max_iter = N => @state] // cap fixed-point loop; transition on exhaustion
#[max_entry = N] // return on (N+1)th entry
#[max_entry = N => @state] // transition on (N+1)th entry
#[trace] // emit log::trace! diagnostics (needs log backend)
Multiple attributes are comma-separated on one line:
#[isolate, max_iter = 1 => @finish]
@handler
...
Isolated states are invisible to the scheduler. They are only entered via an
explicit => @state transition. They must have a defined exit path (transition or
return), or use max_iter with a redirect.
#[isolate]
@error
handle? { return Err("failed"); }
banish! {
#![async]
@fetch
load? { let data = some_future().await; }
}
// The banish! block is now an async expression — must be .awaited
Or use the function attribute to avoid manual wiring:
#[banish::machine] // must be outermost — before #[tokio::main]
#[tokio::main]
async fn main() {
banish! { ... } // async + .await injected automatically; id = "main"
}
use banish::BanishDispatch;
#[derive(BanishDispatch)]
enum Stage { Validate, Process, Finalize }
fn run(order: Order, resume: Stage) -> Result {
banish! {
#![dispatch(resume)] // entry state determined at runtime
@validate ...
@process ...
@finalize done? { return Ok(()); }
#[isolate]
@rejected handle? { return Err("bad"); }
}
}
BanishDispatch maps PascalCase variant names → snake_case state names.
Stage::Validate → "validate" → @validate. Passing a variant with no matching
state panics at runtime.
banish = { version = "1.4.0", features = ["trace-logger"] }
fn main() {
banish::init_trace(None); // stderr
banish::init_trace(Some("t.log")); // file
...
}
Or bring your own log-compatible backend. Diagnostics are emitted as log::trace!.
Cycling between states — use max_iter with a redirect instead of an explicit
loop, to avoid unbounded recursion:
#[max_iter = 1 => @b]
@a attack? { ... }
#[max_iter = 1 => @a]
@b attack? { ... }
One-shot setup per state entry: conditionless rules run once, then never again until the state is re-entered:
@load
init? { data = load_data(); } // fires once
process ? data.is_some() { ... }
Fallback as else-branch that needs a loop: fallbacks do not re-evaluate; use an explicit transition if re-evaluation is needed after the else branch:
valid ? x > 0 { use(x); } !? { x = default; => @reset; }
Rule order is evaluation order: if rule A mutates a value rule B checks, put A before B to make B see the updated value in the same pass.
Pattern conditions bind into the rule body: use name ? let Pat = expr { } to
match and destructure in one step. Combine with !? to handle the non-matching case:
@drain
pop ? let Some(val) = queue.pop() {
process(val);
} !? { return result; }
State-level let resets every entry: don't store cross-entry state there; use
block-level variables instead.
#[banish::machine] must be the outermost attribute: it runs first and transforms
the function before any runtime macro (e.g. #[tokio::main]) sees it.