Hands-On Concurrency with Rust
上QQ阅读APP看书,第一时间看更新

Static and dynamic dispatch

Trait objects likewise have no statically known size in Rust. Trait objects are the mechanism by which Rust performs dynamic dispatch. Preferentially, Rust is a static dispatch language as there are increased opportunities for inlining and optimization—the compiler simply knows more. Consider the following code:

use std::ops::Add;

fn sd_add<T: Add<Output=T>>(x: T, y: T) -> T {
    x + y
}

fn main() {
    assert_eq!(48, sd_add(16 as u8, 32 as u8));
    assert_eq!(48, sd_add(16 as u64, 32 as u64));
}

Here, we define a function, sd_add<T: Add<Output=T>>(x: T, y: T) -> T. Rust, like C++, will perform monomorphization at compile time, emitting two sd_add functions, one for u8 and the other for u64. Like C++, this potentially increases the code size of a Rust program and slows compilation but at the benefit of allowing inlining at the caller site, potentially more efficient implementations owing to type specialization, and fewer branches.

When static dispatch is not desirable, the programmer can construct trait objects to perform dynamic dispatch. Trait objects do not have a known size—indeed, they can be any T that implements the trait—and so, like slices, must exist behind a kind of pointer. Dynamic dispatch will not see much use in this book. The reader is warmly encouraged to consult the documentation for std::raw::TraitObject for full details on Rust's trait object notion.