Practical Crates
Dependency Adapter
Program to a Trait
A crate boundary is easier to test when application code depends on a trait instead of a concrete external client.
Program
Play the program to choose a retry count and see the client wrapped behind a small trait.
dependency_adapter.rs
Replay: real traced execution (multi-file project)
fn main() {
let retries = 2;
let client = MockClient { status: "ok" };
let message = fetch_status(&client, retries);
println!("{message}");
}
trait StatusClient {
fn status(&self) -> &'static str;
}
struct MockClient {
status: &'static str,
}
impl StatusClient for MockClient {
fn status(&self) -> &'static str {
self.status
}
}
fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
format!("{} after {} retries", client.status(), retries)
}
fn main() {
let retries = 0;
let client = MockClient { status: "ok" };
let message = fetch_status(&client, retries);
println!("{message}");
}
trait StatusClient {
fn status(&self) -> &'static str;
}
struct MockClient {
status: &'static str,
}
impl StatusClient for MockClient {
fn status(&self) -> &'static str {
self.status
}
}
fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
format!("{} after {} retries", client.status(), retries)
}
fn main() {
let retries = 4;
let client = MockClient { status: "ok" };
let message = fetch_status(&client, retries);
println!("{message}");
}
trait StatusClient {
fn status(&self) -> &'static str;
}
struct MockClient {
status: &'static str,
}
impl StatusClient for MockClient {
fn status(&self) -> &'static str {
self.status
}
}
fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
format!("{} after {} retries", client.status(), retries)
}
retries ← 2, client ← (empty)
1fn main() {2 let retrie→ 2s = 2; //@retries=2, 0, 43 let clien→ (empty)t = MockClient { status: "ok" };4 let message = fetch_status(&clien(empty)t, retrie2s);5 println!("{message}");message ← "ok after 2 retries"
3 let client = MockClient { status: "ok" };4 let messag→ "ok after 2 retries"e = fetch_status(&clien(empty)t, retrie2s);5 println!("{message}");6}outputok after 2 retries
retries ← 0, client ← (empty)
1fn main() {2 let retrie→ 0s = 0;3 let clien→ (empty)t = MockClient { status: "ok" };4 let message = fetch_status(&clien(empty)t, retrie0s);5 println!("{message}");message ← "ok after 0 retries"
3 let client = MockClient { status: "ok" };4 let messag→ "ok after 0 retries"e = fetch_status(&clien(empty)t, retrie0s);5 println!("{message}");6}outputok after 0 retries
retries ← 4, client ← (empty)
1fn main() {2 let retrie→ 4s = 4;3 let clien→ (empty)t = MockClient { status: "ok" };4 let message = fetch_status(&clien(empty)t, retrie4s);5 println!("{message}");message ← "ok after 4 retries"
3 let client = MockClient { status: "ok" };4 let messag→ "ok after 4 retries"e = fetch_status(&clien(empty)t, retrie4s);5 println!("{message}");6}outputok after 4 retries
adapter
`StatusClient` is the stable boundary that application code depends on.
mock
`MockClient` lets the example run without a network client or third-party crate.
dependency seam
`fetch_status` can accept another client later if it implements the same trait.