A function can accept a trait reference so callers provide behavior without exposing concrete types.

Program

Play the program to choose a status code and format it through a trait boundary.

code
trait_formatter_boundary.rs
Replay: real traced execution (multi-file project)
trait Formatter {
    fn status(&self, code: u16) -> String;
}

struct PlainFormatter;

impl Formatter for PlainFormatter {
    fn status(&self, code: u16) -> String {
        format!("status={code}")
    }
}

fn render(formatter: &dyn Formatter, code: u16) -> String {
    formatter.status(code)
}

fn main() {
    let code = 200;
    let formatter = PlainFormatter;
    let text = render(&formatter, code);
    println!("{text}");
}
trait Formatter {
    fn status(&self, code: u16) -> String;
}

struct PlainFormatter;

impl Formatter for PlainFormatter {
    fn status(&self, code: u16) -> String {
        format!("status={code}")
    }
}

fn render(formatter: &dyn Formatter, code: u16) -> String {
    formatter.status(code)
}

fn main() {
    let code = 404;
    let formatter = PlainFormatter;
    let text = render(&formatter, code);
    println!("{text}");
}
trait Formatter {
    fn status(&self, code: u16) -> String;
}

struct PlainFormatter;

impl Formatter for PlainFormatter {
    fn status(&self, code: u16) -> String {
        format!("status={code}")
    }
}

fn render(formatter: &dyn Formatter, code: u16) -> String {
    formatter.status(code)
}

fn main() {
    let code = 500;
    let formatter = PlainFormatter;
    let text = render(&formatter, code);
    println!("{text}");
}
  1. code ← 200, formatter ← (empty)

    17fn main() {18    let cod→ 200e = 200; //@code=200, 404, 50019    let formatte→ (empty)r = PlainFormatter;20    let text = render(&formatte(empty)r, cod200e);21    println!("{text}");
  2. text ← "status=200"

    19    let formatter = PlainFormatter;20    let tex→ "status=200"t = render(&formatte(empty)r, cod200e);21    println!("{text}");22}
    outputstatus=200
  1. code ← 404, formatter ← (empty)

    17fn main() {18    let cod→ 404e = 404;19    let formatte→ (empty)r = PlainFormatter;20    let text = render(&formatte(empty)r, cod404e);21    println!("{text}");
  2. text ← "status=404"

    19    let formatter = PlainFormatter;20    let tex→ "status=404"t = render(&formatte(empty)r, cod404e);21    println!("{text}");22}
    outputstatus=404
  1. code ← 500, formatter ← (empty)

    17fn main() {18    let cod→ 500e = 500;19    let formatte→ (empty)r = PlainFormatter;20    let text = render(&formatte(empty)r, cod500e);21    println!("{text}");
  2. text ← "status=500"

    19    let formatter = PlainFormatter;20    let tex→ "status=500"t = render(&formatte(empty)r, cod500e);21    println!("{text}");22}
    outputstatus=500
trait `Formatter` describes behavior without naming a concrete implementation.
trait object `&dyn Formatter` accepts any formatter behind a shared reference.
boundary `render` depends on formatting behavior, not on the `PlainFormatter` type.