You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
2.8 KiB
Markdown
93 lines
2.8 KiB
Markdown
4 years ago
|
# 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
|
||
|
|
||
3 years ago
|
```rust
|
||
|
use std::io;
|
||
|
use std::fs;
|
||
|
|
||
|
# fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
|
# let arg = "-";
|
||
3 years ago
|
|
||
4 years ago
|
// 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.
|
||
3 years ago
|
let readable: &mut dyn io::Read = if arg == "-" {
|
||
4 years ago
|
stdin_read = io::stdin();
|
||
|
&mut stdin_read
|
||
|
} else {
|
||
|
file_read = fs::File::open(arg)?;
|
||
|
&mut file_read
|
||
|
};
|
||
|
|
||
|
// Read from `readable` here.
|
||
3 years ago
|
|
||
|
# Ok(())
|
||
|
# }
|
||
4 years ago
|
```
|
||
|
|
||
|
## 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
|
||
3 years ago
|
whole code that follows to work with both `File` or `Stdin`.
|
||
4 years ago
|
|
||
|
## Disadvantages
|
||
|
|
||
|
The code needs more moving parts than the `Box`-based version:
|
||
|
|
||
3 years ago
|
```rust,ignore
|
||
4 years ago
|
// We still need to ascribe the type for dynamic dispatch.
|
||
|
let readable: Box<dyn io::Read> = 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
|
||
1 year ago
|
initialized _before use_, so it's easy to overlook the fact that _unused_
|
||
4 years ago
|
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:
|
||
|
|
||
1 year ago
|
- 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
|
||
4 years ago
|
|
||
|
## See also
|
||
|
|
||
1 year ago
|
- [Finalisation in destructors](dtor-finally.md) and
|
||
|
[RAII guards](../patterns/behavioural/RAII.md) can benefit from tight control over
|
||
|
lifetimes.
|
||
|
- For conditionally filled `Option<&T>`s of (mutable) references, one can
|
||
|
initialize an `Option<T>` directly and use its [`.as_ref()`] method to get an
|
||
|
optional reference.
|
||
4 years ago
|
|
||
|
[`.as_ref()`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.as_ref
|