Hello everyone,
I'm very new to rust and systems programming, so apologies if this is just fundamentally flawed thinking.
Permise
Suppose I have this trait -
trait Animal {
fn say(&self);
}
And some implementers -
struct Dog {}
impl Animal for Dog {
fn say(&self) {
println!("Woof!");
}
}
struct Cat {}
impl Animal for Cat {
fn say(&self) {
println!("Meow!");
}
}
Interface analogy
Now how do I write code that is smart enough to work with either a Cat or a Dog?
If traits are anything like interfaces -- this should be easy!
let animal: Animal;
animal = Dog { };
animal.say();
But no, rust doesn't like that.
error[E0308]: mismatched types
--> src/main.rs:25:14
|
25 | animal = Dog { };
| ^^^^^^^ expected trait object `dyn Animal`, found struct `Dog`
|
= note: expected trait object `dyn Animal`
found struct `Dog`
error[E0277]: the size for values of type `dyn Animal` cannot be known at compilation time
--> src/main.rs:24:9
|
24 | let animal: Animal;
| ^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `dyn Animal`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: all local variables must have a statically known size
= help: unsized locals are gated as an unstable feature
error[E0277]: the size for values of type `dyn Animal` cannot be known at compilation time
--> src/main.rs:25:5
|
25 | animal = Dog { };
| ^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `dyn Animal`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: the left-hand-side of an assignment must have a statically known size
Box
A quick google leads me to Box<dyn T>
, which isn't pretty, but certainly works -
let animal: Box<dyn Animal>;
animal = Box::new(Dog { });
animal.say();
Woof!
Until one day...Generics
Generics
A library gives me this nice function to create a couple of dogs -
fn get_n_dogs(n: usize) -> Vec<Dog> {
let mut dogs = Vec::with_capacity(n);
for _ in 0..n {
dogs.push(Dog { });
}
dogs
}
But oh, now I can't use it without explicitly speficying the type of the animal!
let animals: Vec<Box<dyn Animal>>;
animals = get_n_dogs(5);
for animal in animals {
animal.say();
}
error[E0308]: mismatched types
--> src/main.rs:25:15
|
25 | animals = get_n_dogs(5);
| ^^^^^^^^^^^^^ expected struct `std::boxed::Box`, found struct `Dog`
|
= note: expected struct `std::vec::Vec<std::boxed::Box<dyn Animal>>`
found struct `std::vec::Vec<Dog>`
Question
With this restriction, the (potentically huge) refactoring needed for a possible get_n_cats(5)
seems too barbaric.
Is this really the end of the interface analogy? Is rust just too static to not let me write code like this?
Appendix
This can also be interpreted as "reverse generics", where the resolved type comes from the bottom-up, not top-down.
Something like this (hypothetical code) -
fn main() {
let animals: Vec<Animal>; // eventually resolved to Vec<Dog> because of get_n_dogs()
animals = top();
}
fn top<T: Animal>() -> Vec<T> {
return bottom();
}
fn bottom<T: Animal>() -> Vec<T> {
return get_n_dogs();
}