Examples
This document points to the example programs and common usage patterns.
Example Index
Top level examples in examples/:
adder.rsfor a simple request response protocolalternating_bit.rsfor a reliable message delivery patternasync_subtyping.rsfor async-subtyping checks and examplesbounded_recursion.rsfor bounded recursion strategiesclient_server_log.rsfor logging in a client server protocolring.rsandring_choice.rsfor ring topologies and branchingthree_adder.rsfor a three-party aggregation flowdouble_buffering.rsandelevator.rsfor multi step coordinationfft.rsfor distributed computationoauth.rsfor a multi role authentication flowwasm-ping-pong/for browser builds
Advanced examples live under examples/advanced_features/ and runnable bundles under examples/running_examples/.
Common Patterns
Request Response
#![allow(unused)]
fn main() {
choreography!(r#"
protocol RequestResponse =
roles Client, Server
Client -> Server : Request
Server -> Client : Response
"#);
}
Use this pattern when the client waits for a reply before continuing.
Choice
#![allow(unused)]
fn main() {
choreography!(r#"
protocol ChoicePattern =
roles Client, Server
case choose Server of
Accept ->
Server -> Client : Confirmation
Reject ->
Server -> Client : Rejection
"#);
}
Only the deciding role selects the branch. Other roles react to that choice.
Loops
#![allow(unused)]
fn main() {
choreography!(r#"
protocol LoopPattern =
roles Client, Server
loop repeat 5
Client -> Server : Request
Server -> Client : Response
"#);
}
Use bounded loops for batch workflows or retries.
Parallel Branches
#![allow(unused)]
fn main() {
choreography!(r#"
protocol ParallelPattern =
roles Coordinator, Worker1, Worker2
branch
Coordinator -> Worker1 : Task
branch
Coordinator -> Worker2 : Task
"#);
}
Parallel branches must be independent in order to remain well formed.
Testing Patterns
Unit Test With InMemoryHandler
#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_send_only() {
let mut handler = InMemoryHandler::new(Role::Alice);
let program = Program::new()
.send(Role::Bob, TestMessage)
.end();
let mut endpoint = ();
let result = interpret(&mut handler, &mut endpoint, program).await;
assert!(result.is_ok());
}
}
Use InMemoryHandler::with_channels and shared channel maps when a test needs both send and receive.
Integration Test With TelltaleHandler
#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_session_types() {
let (alice_ch, bob_ch) = SimpleChannel::pair();
let mut alice_ep = TelltaleEndpoint::new(Role::Alice);
alice_ep.register_channel(Role::Bob, alice_ch);
let mut bob_ep = TelltaleEndpoint::new(Role::Bob);
bob_ep.register_channel(Role::Alice, bob_ch);
let mut handler = TelltaleHandler::<Role, Message>::new();
// run protocol on both endpoints
}
}
This pattern validates the channel based handler without custom transports. Ensure the role type implements both telltale::Role and RoleId.
RecordingHandler
#![allow(unused)]
fn main() {
let handler = RecordingHandler::new(Role::Alice);
let events = handler.events();
}
RecordingHandler captures send, recv, choose, and offer events for assertions. It does not generate values, so use it for structural tests.
Fault Injection
Fault injection is available behind the test-utils feature.
#![allow(unused)]
fn main() {
let base = InMemoryHandler::new(Role::Alice);
let mut handler = FaultInjection::new(base, 0.1);
}
Use this to validate retry behavior and error handling.
Enable this by adding features = ["test-utils"] on telltale-choreography in test builds.
Running Examples
Run a single example with Cargo.
cargo run --example adder
The wasm-ping-pong example uses its own build script.
cd examples/wasm-ping-pong
./build.sh
See the comments in each example file for setup requirements.