From 399c38ee2b2a3f34ca594bc3c5e5af1d86516168 Mon Sep 17 00:00:00 2001 From: Andre Bogus Date: Tue, 24 Mar 2020 07:43:37 +0100 Subject: [PATCH] New idiom: Out of the Box Dynamic Dispatch --- README.md | 1 + idioms/on-stack-dyn-dispatch.md | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 idioms/on-stack-dyn-dispatch.md diff --git a/README.md b/README.md index 26b3629..95eac97 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ language. * [Pass variables to closure](idioms/pass-var-to-closure.md) * [`mem::replace(_)` to avoid needless clones](idioms/mem-replace.md) * [Temporary mutability](idioms/temporary-mutability.md) +* [On-Stack Dynamic Dispatch](idioms/on-stack-dyn-dispatch.md) * TODO FFI usage (By being mindful of how to provide Rust libraries, and make use of existing libraries across the FFI, you can get more out of benefits Rust can bring) ### Design patterns diff --git a/idioms/on-stack-dyn-dispatch.md b/idioms/on-stack-dyn-dispatch.md new file mode 100644 index 0000000..b1a8b43 --- /dev/null +++ b/idioms/on-stack-dyn-dispatch.md @@ -0,0 +1,83 @@ +# On-Stack Dynamic Dispatch + +## Description + +We can dynamically dispatch over multiple values, however, to do so, we need +to declare multiple variables to bind differently-typed objects. To extend the +lifetime as necessary, we can use deferred conditional initialization, as seen +below: + +## Example + +```rust +// These must live longer than `readable`, and thus are declared first: +let (mut stdin_read, mut file_read); + +// We need to ascribe the type to get dynamic dispatch. +let readable: &mut dyn io::Read = if arg == '-' { + stdin_read = io::stdin(); + &mut stdin_read +} else { + file_read = fs::File::open(arg)?; + &mut file_read +}; + +// Read from `readable` here. +``` + +## Motivation + +Rust monomorphises code by default. This means a copy of the code will be +generated for each type it is used with and optimized independently. While this +allows for very fast code on the hot path, it also bloats the code in places +where performance is not of the essence, thus costing compile time and cache +usage. + +Luckily, Rust allows us to use dynamic dispatch, but we have to explicitly ask +for it. + +## Advantages + +We do not need to allocate anything on the heap. Neither do we need to +initialize something we won't use later, nor do we need to monomorphize the +whole code that follows to work with both `File` or `Stdin`, with all the + +## Disadvantages + +The code needs more moving parts than the `Box`-based version: + +``` +// We still need to ascribe the type for dynamic dispatch. +let readable: Box = if arg == "-" { + Box::new(io::stdin()) +} else { + Box::new(fs::File::open(arg)?) +}; +// Read from `readable` here. +``` + +## Discussion + +Rust newcomers will usually learn that Rust requires all variables to be +initialized *before use*, so it's easy to overlook the fact that *unused* +variables may well be uninitialized. Rust works quite hard to ensure that this +works out fine and only the initialized values are dropped at the end of their +scope. + +The example meets all the constraints Rust places on us: + +* All variables are initialized before using (in this case borrowing) them +* Each variable only holds values of a single type. In our example, `stdin` is +of type `Stdin`, `file` is of type `File` and `readable` is of type `&mut dyn +Read` +* Each borrowed value outlives all the references borrowed from it + +## See also + +* [Finalisation in destructors](idioms/dtor-finally.md) and +[RAII guards](patterns/RAII.md) can benefit from tight control over lifetimes. +* For conditionally filled `Option<&T>`s of (mutable) references, one can +initialize an `Option` directly and use its [`.as_ref()`] method to get an +optional reference. + +[`.as_ref()`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.as_ref