CodeBlanks Logo Icon CodeBlanks

Static vs dynamic dispatch

Learn the difference and cost of static vs dynamic dispatch.

Static and dynamic dispatch

The same behaviour can be written in two ways:

fn statik<T: Ses>(x: &T) -> String {   // a separate copy for each T
    x.ses()                              // direct, can be inlined
}

fn dinamik(x: &dyn Ses) -> String {     // a single copy
    x.ses()                              // through the vtable
}

Static: faster calls, the compiler can inline and optimize; the price is a bigger binary and compile time. Dynamic: one copy of code, mixed collections, smaller binary; the price is an indirect call and missed optimizations.

How to choose?

The default choice is generics. Switch to dyn when: you need different types in a collection, the type is decided at runtime (plugins, configuration), compile time or binary size has become a problem, or you don't want the API polluted with generic parameters. The performance difference is too small to measure in most applications; measure in hot loops.

// mixed: generics aren't enough
let v: Vec<Box<dyn Ses>> = vec![Box::new(Kedi), Box::new(Kopek)];
// single type: generics are enough
fn hepsi<T: Ses>(v: &[T]) -> Vec<String> { v.iter().map(|x| x.ses()).collect() }

Practice Two dispatches

Write the statik and dinamik functions above. Call both with Kedi and print as statik: .. | dinamik: ...

Starter code

main.rs
trait Ses {
    fn ses(&self) -> String;
}

struct Kedi;
struct Kopek;

impl Ses for Kedi {
    fn ses(&self) -> String {
        String::from("Miyav")
    }
}

impl Ses for Kopek {
    fn ses(&self) -> String {
        String::from("Hav")
    }
}

fn main() {
    // buraya yaz
}

Sample solution

main.rs
trait Ses {
    fn ses(&self) -> String;
}

struct Kedi;
struct Kopek;

impl Ses for Kedi {
    fn ses(&self) -> String {
        String::from("Miyav")
    }
}

impl Ses for Kopek {
    fn ses(&self) -> String {
        String::from("Hav")
    }
}

fn statik<T: Ses>(x: &T) -> String {
    x.ses()
}

fn dinamik(x: &dyn Ses) -> String {
    x.ses()
}

fn main() {
    let k = Kedi;
    println!("statik: {} | dinamik: {}", statik(&k), dinamik(&k));
}

Expected output

statik: Miyav | dinamik: Miyav

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 If a function takes <code>&amp;dyn Ses</code>, can a <code>&amp;Kedi</code> be passed to it?

  • With as
  • Yes; &Kedi converts automatically to &dyn Ses (unsize coercion) Correct answer
  • No
  • Only with a Box

Explanation: The compiler adds the vtable pointer in the conversion; the reverse (dyn → concrete) needs a downcast with Any.

2 Static dispatch

Question
fn statik____(x: &T) -> String {
    x.ses()
}
Answer
fn statik<T: Ses>(x: &T) -> String {
    x.ses()
}

Explanation: A generic bound is static dispatch, generating a separate copy for each type.

3 Dispatch and property

Term Meaning
Statik (generic) Can be inlined
Dinamik (dyn) Call via vtable
Monomorfizasyon A copy per type
Unsize coercion &Kedi → &dyn Ses

All lessons in Advanced