I have various structs that all implement the same trait. I want to branch on some condition, deciding at runtime which of those structs to instantiate. Then, regardless of which branch I followed, I want to call methods from that trait.
Is this possible in Rust? I'm hoping to achieve something like the following (which does not compile):
trait Barks {
fn bark(&self);
}
struct Dog;
impl Barks for Dog {
fn bark(&self) {
println!("Yip.");
}
}
struct Wolf;
impl Barks for Wolf {
fn bark(&self) {
println!("WOOF!");
}
}
fn main() {
let animal: Barks;
if 1 == 2 {
animal = Dog;
} else {
animal = Wolf;
}
animal.bark();
}
Yes, but not that easily. What you've written there is that animal should be a variable of type Barks, but Barks is a trait; a description of an interface. Traits don't have a statically-defined size, since a type of any size could come along and impl Barks. The compiler has no idea how big to make animal.
What you need to do is add a layer of indirection. In this case, you can use Box, although you can also use things like Rc or plain references:
fn main() {
let animal: Box<dyn Barks>;
if 1 == 2 {
animal = Box::new(Dog);
} else {
animal = Box::new(Wolf);
}
animal.bark();
}
Here, I'm allocating the Dog or Wolf on the heap, then casting that up to a Box<dyn Barks>. This is kind of like casting an object to an interface in something like C# or Java, or casting a Dog* to a Barks* in C++.
An entirely different approach you could also use would be enums. You could have enum Animal { Dog, Wolf } then define an impl Animal { fn bark(&self) { ... } }. Depends on whether you need a completely open-ended set of animals and/or multiple traits.
Finally, note that "kind of" above. There are various things that don't work as they would in Java/C#/C++. For example, Rust doesn't have downcasting (you can't go from Box<dyn Barks> back to Box<Dog>, or from one trait to another). Also, this only works if the trait is "object safe" (no generics, no using self or Self by-value).
DK has a good explanation, I'll just chime in with an example where we allocate the Dog or Wolf on the stack, avoiding a heap allocation:
fn main() {
let dog;
let wolf;
let animal: &dyn Barks = if 1 == 2 {
dog = Dog;
&dog
} else {
wolf = Wolf;
&wolf
};
animal.bark();
}
It's a bit ugly, but the references accomplish the same indirection as a Box with a smidge less overhead.
See also:
How can I conditionally provide a default reference without performing unnecessary computation when it isn't used?
How do I make format! return a &str from a conditional expression?
Defining a custom enumeration is the most efficient way to do this. This will allow you to allocate on the stack exactly the amount of space you need, i.e. the size of the largest option, plus 1 extra byte to track which option is stored. It also allows direct access without a level of indirection, unlike solutions using a Box or a trait reference.
Unfortunately, it does require more boiler-plate:
enum WolfOrDog {
IsDog(Dog),
IsWolf(Wolf)
}
use WolfOrDog::*;
impl Barks for WolfOrDog {
fn bark(&self) {
match *self {
IsDog(ref d) => d.bark(),
IsWolf(ref w) => w.bark()
}
}
}
fn main() {
let animal: WolfOrDog;
if 1 == 2 {
animal = IsDog(Dog);
} else {
animal = IsWolf(Wolf);
}
animal.bark();
}
In main we use only a single stack allocated variable, holding an instance of our custom enumeration.
Related
I am writing a Rust application. I'd like to have a method to display the text whether it is a string or number. Furthermore, I came up with the following solution, but it is duplicating the code. Is there a better way to do it in Rust?
Notice: I am not looking for a built-in function to print variables. It's just an example. I am looking for a way to implement the same feature for two types.
trait Display {
fn display(self);
}
impl Display for String {
fn display(self) -> () {
println!("You wrote: {}", self);
}
}
impl Display for i32 {
fn display(self) -> () {
println!("You wrote: {}", self);
}
}
fn main() {
let name: String = String::from("Tom Smykowski");
name.display();
let age: i32 = 22;
age.display();
}
You came close. But there is already a trait for converting things to strings - std::fmt::Display (and the automatically-implemented ToString) - so you don't need to have your own trait:
fn display<T: std::fmt::Display>(v: T) {
println!("You wrote: {v}");
}
fn main() {
let name: String = String::from("Tom Smykowski");
display(name);
let age: i32 = 22;
display(age);
}
Even if you don't need to display the types but do something else with them, we can take the idea from Display - instead of defining the whole functionality, define only the pieces that are different. For example, you can create a trait to convert the numbers to strings (or the opposite), or just have functions for each different piece - for example, printing itself without "You wrote: ".
I came up with the following solution, but it is duplicating the code. Is there a better way to do it in Rust?
Add a simple declarative macro on top, that is very common in the stdlib and all. e.g.
macro_rules! impl_display {
($t:ty) => {
impl Display for $t {
fn display(self) {
println!("You wrote {self}");
}
}
}
}
impl_display!(String);
impl_display!(i32);
impl_display!(i64);
impl_display!(f32);
Although:
usually the implementations would be different, though not always e.g. implementing an operation on all numeric types, or all unsigned numbers, that's one of the most common context you'll see it in the stdlib: the stdlib has no numeric trait but methods are usually implemented on all numeric types, so there's a handful of macros used for all of them, and when new methods are added they're just added to the relevant macro
here you're already relying on the existence and implementation of std::fmt::Display so you should just use that, your trait is not really useful
I am writing a program which will receive user input from CSV or JSON (that doesn't really matter). There are potentially many inputs (each line of a CSV for example), which would reference different structs. So, I need to return an instance of a struct for each input string, but I don't know upfront which struct that would be. My attempt (code doesn't compile):
fn main () {
let zoo: Vec<Box<dyn Animal>>;
let user_input = "Cat,Persik";
let user_input = user_input.split(",");
match user_input.nth(0) {
"Cat" => zoo.push(Cat(user_input.nth(0))),
_ => zoo.push(Dog(user_input.nth(0))) //here user would be expected to provide a u8
}
}
trait Animal {}
struct Dog {
age: u8,
}
impl Animal for Dog {}
struct Cat {
name: String,
}
impl Animal for Cat {}
One way to do it is with if statements like this. But if there are hundreds of animals that would make the code pretty ugly. I have a macro which returns struct name for an instance, but I couldn't figure out a way to use that. I also thought about using enum for this, but couldn't figure out either.
Is there a shorter and more concise way of doing this?
Doesn't this way limit me in using only methods defined in the Animal trait on items of zoo? If so, is there a way around this constraint?
Essentially, I want to get a vector of structs, and to be able to use their methods freely. I don't know how many there will be, and I don't know in advance which structs exactly.
It's often helpful to use helper functions for parsing things. We can implement this function on the trait itself to keep the parsing function associated with the trait.
We'll have the function return Result<Box<dyn animal>, ()> since it's possible for parsing to fail. (We'd probably want a proper error type instead of () in real code.)
trait animal{}
impl dyn animal {
fn try_parse(kind: &str, data: &str) -> Result<Box<dyn animal>, ()> {
match kind {
"Cat" => Ok(Box::new(Cat { name: data.into() })),
"Dog" => Ok(Box::new(Dog { age: data.parse().map_err(|_e| ())? })),
_ => Err(()),
}
}
}
Ok, so now we have a function that can be used to parse a single animal, and has a way to signal failure. We could now parse a comma-separated string building off of this function, again signaling errors if the string doesn't contain a comma:
impl dyn animal {
// try_parse()
fn try_parse_comma_separated(input: &str) -> Result<Box<dyn animal>, ()> {
let split = input.split(',');
let parts = (split.next(), split.next());
// parts is a tuple of two Option<&str>. We can only proceed if both
// are Some.
match parts {
(Some(kind), Some(data)) => Self::try_parse(kind, data),
_ => Err(()),
}
}
}
Now our main() is trivial:
fn main() {
let mut zoo: Vec<Box<dyn animal>> = vec![];
let user_input = "Cat,Persik";
zoo.push(<dyn animal>::try_parse_comma_separated(user_input).unwrap());
}
Separating things out like this allows us to reuse these functions in other interesting ways. Let's say you wanted to parse a string like "Cat,Persik,Dog,5" as two values. That can now be done by using iterators and mapping over our parse function:
fn main() {
let user_input = "Cat,Persik,Dog,5";
let zoo = user_input.split(',').collect::<Vec<_>>()
.chunks_exact(2) // Group the input into slices of 2 elements each
.map(|s| <dyn animal>::try_parse(s[0], s[1]).unwrap())
.collect::<Vec<_>>();
}
To answer your question about a better way to do this when managing many implementors of animal, you could move the implementation-specific parsing logic into a similar function on each implementation instead, and call that functionality from <dyn animal>::try_parse(). The parsing logic has to live somewhere.
Doesn't this way limit me in using only methods defined in animal Trait on items of zoo? If so, is there a way around this constraint?
Without downcasting, yes. Generally when you have a collection of polymorphic values like dyn animal, you want to use them polymorphically -- invoking only methods defined on the animal trait. Each implementation of the trait on a specific type can implement the trait's interface however it makes sense for that animal.
Downcasting is non-trivial, but with a helper trait it becomes a bit more palatable:
trait AsAny {
fn as_any(&self) -> &dyn Any;
}
impl<T: 'static + animal> AsAny for T {
fn as_any(&self) -> &dyn Any { self }
}
trait animal: AsAny { }
Now, given an animal: Box<dyn Animal> you can use animal.as_any().downcast_ref::<Dog>() for example, which gives you back an Option<&Dog>. This will be None if the boxed animal isn't a dog. Based on the zoo in the last example (with a dog and a cat):
let dogs = zoo.iter()
// Filter down the zoo to just dogs (produces a sequence of &Dog)
.filter_map(|animal| animal.as_any().downcast_ref::<Dog>());
// We should only find one dog in the zoo.
assert_eq!(dogs.count(), 1);
But this should be an absolute last resort when using your animals polymorphically isn't an option.
I have a program that involves examining a complex data structure to see if it has any defects. (It's quite complicated, so I'm posting example code.) All of the checks are unrelated to each other, and will all have their own modules and tests.
More importantly, each check has its own error type that contains different information about how the check failed for each number. I'm doing it this way instead of just returning an error string so I can test the errors (it's why Error relies on PartialEq).
My Code So Far
I have traits for Check and Error:
trait Check {
type Error;
fn check_number(&self, number: i32) -> Option<Self::Error>;
}
trait Error: std::fmt::Debug + PartialEq {
fn description(&self) -> String;
}
And two example checks, with their error structs. In this example, I want to show errors if a number is negative or even:
#[derive(PartialEq, Debug)]
struct EvenError {
number: i32,
}
struct EvenCheck;
impl Check for EvenCheck {
type Error = EvenError;
fn check_number(&self, number: i32) -> Option<EvenError> {
if number < 0 {
Some(EvenError { number: number })
} else {
None
}
}
}
impl Error for EvenError {
fn description(&self) -> String {
format!("{} is even", self.number)
}
}
#[derive(PartialEq, Debug)]
struct NegativeError {
number: i32,
}
struct NegativeCheck;
impl Check for NegativeCheck {
type Error = NegativeError;
fn check_number(&self, number: i32) -> Option<NegativeError> {
if number < 0 {
Some(NegativeError { number: number })
} else {
None
}
}
}
impl Error for NegativeError {
fn description(&self) -> String {
format!("{} is negative", self.number)
}
}
I know that in this example, the two structs look identical, but in my code, there are many different structs, so I can't merge them. Lastly, an example main function, to illustrate the kind of thing I want to do:
fn main() {
let numbers = vec![1, -4, 64, -25];
let checks = vec![
Box::new(EvenCheck) as Box<Check<Error = Error>>,
Box::new(NegativeCheck) as Box<Check<Error = Error>>,
]; // What should I put for this Vec's type?
for number in numbers {
for check in checks {
if let Some(error) = check.check_number(number) {
println!("{:?} - {}", error, error.description())
}
}
}
}
You can see the code in the Rust playground.
Solutions I've Tried
The closest thing I've come to a solution is to remove the associated types and have the checks return Option<Box<Error>>. However, I get this error instead:
error[E0038]: the trait `Error` cannot be made into an object
--> src/main.rs:4:55
|
4 | fn check_number(&self, number: i32) -> Option<Box<Error>>;
| ^^^^^ the trait `Error` cannot be made into an object
|
= note: the trait cannot use `Self` as a type parameter in the supertraits or where-clauses
because of the PartialEq in the Error trait. Rust has been great to me thus far, and I really hope I'm able to bend the type system into supporting something like this!
When you write an impl Check and specialize your type Error with a concrete type, you are ending up with different types.
In other words, Check<Error = NegativeError> and Check<Error = EvenError> are statically different types. Although you might expect Check<Error> to describe both, note that in Rust NegativeError and EvenError are not sub-types of Error. They are guaranteed to implement all methods defined by the Error trait, but then calls to those methods will be statically dispatched to physically different functions that the compiler creates (each will have a version for NegativeError, one for EvenError).
Therefore, you can't put them in the same Vec, even boxed (as you discovered). It's not so much a matter of knowing how much space to allocate, it's that Vec requires its types to be homogeneous (you can't have a vec![1u8, 'a'] either, although a char is representable as a u8 in memory).
Rust's way to "erase" some of the type information and gain the dynamic dispatch part of subtyping is, as you discovered, trait objects.
If you want to give another try to the trait object approach, you might find it more appealing with a few tweaks...
You might find it much easier if you used the Error trait in std::error instead of your own version of it.
You may need to impl Display to create a description with a dynamically built String, like so:
impl fmt::Display for EvenError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} is even", self.number)
}
}
impl Error for EvenError {
fn description(&self) -> &str { "even error" }
}
Now you can drop the associated type and have Check return a trait object:
trait Check {
fn check_number(&self, number: i32) -> Option<Box<Error>>;
}
your Vec now has an expressible type:
let mut checks: Vec<Box<Check>> = vec![
Box::new(EvenCheck) ,
Box::new(NegativeCheck) ,
];
The best part of using std::error::Error...
is that now you don't need to use PartialEq to understand what error was thrown. Error has various types of downcasts and type checks if you do need to retrieve the concrete Error type out of your trait object.
for number in numbers {
for check in &mut checks {
if let Some(error) = check.check_number(number) {
println!("{}", error);
if let Some(s_err)= error.downcast_ref::<EvenError>() {
println!("custom logic for EvenErr: {} - {}", s_err.number, s_err)
}
}
}
}
full example on the playground
I eventually found a way to do it that I'm happy with. Instead of having a vector of Box<Check<???>> objects, have a vector of closures that all have the same type, abstracting away the very functions that get called:
fn main() {
type Probe = Box<Fn(i32) -> Option<Box<Error>>>;
let numbers: Vec<i32> = vec![ 1, -4, 64, -25 ];
let checks = vec![
Box::new(|num| EvenCheck.check_number(num).map(|u| Box::new(u) as Box<Error>)) as Probe,
Box::new(|num| NegativeCheck.check_number(num).map(|u| Box::new(u) as Box<Error>)) as Probe,
];
for number in numbers {
for check in checks.iter() {
if let Some(error) = check(number) {
println!("{}", error.description());
}
}
}
}
Not only does this allow for a vector of Box<Error> objects to be returned, it allows the Check objects to provide their own Error associated type which doesn't need to implement PartialEq. The multiple ases look a little messy, but on the whole it's not that bad.
I'd suggest you some refactoring.
First, I'm pretty sure, that vectors should be homogeneous in Rust, so there is no way to supply elements of different types for them. Also you cannot downcast traits to reduce them to a common base trait (as I remember, there was a question about it on SO).
So I'd use algebraic type with explicit match for this task, like this:
enum Checker {
Even(EvenCheck),
Negative(NegativeCheck),
}
let checks = vec![
Checker::Even(EvenCheck),
Checker::Negative(NegativeCheck),
];
As for error handling, consider use FromError framework, so you will able to involve try! macro in your code and to convert error types from one to another.
My goal is to have a reference counted struct which is referred as a trait in one context and by its concrete type in another. Best explained in code:
#![feature(box_syntax)]
use std::rc::Rc;
use std::cell::RefCell;
trait Employee {
fn be_managed(&mut self);
}
struct Human;
impl Human {
fn be_human(&mut self) {
println!("I'm just a human who needs a mutable self sometimes");
}
}
impl Employee for Human {
fn be_managed(&mut self) {
println!("Off to the salt mines");
}
}
struct Manager {
my_employee: Rc<RefCell<Box<Employee + 'static>>>, //'
}
fn main() {
let mut human1 = Rc::new(RefCell::new(box Human as Box<Employee>));
let manager1 = Manager {
my_employee: human1.clone(), // This works due to cast above
};
manager1.my_employee.borrow_mut().be_managed();
human1.borrow_mut().be_human(); // But we can't be human anymore
let mut human2 = Rc::new(RefCell::new(box Human));
let manager2 = Manager {
my_employee: human2.clone(), // This doesn't work
};
manager2.my_employee.borrow_mut().be_managed();
human2.borrow_mut().be_human();
}
I want the Manager to be able to have any struct implementing the Employee trait as my_employee, but other references should still be able to call other methods on the original object, ie be_human.
Right now I'm getting the following errors from the above code:
src/main.rs:37:25: 37:35 error: type `core::cell::RefMut<'_, Box<Employee>>` does not implement any method in scope named `be_human`
src/main.rs:37 human1.borrow_mut().be_human(); // But we can't be human anymore
^~~~~~~~~~
src/main.rs:44:22: 44:36 error: mismatched types:
expected `alloc::rc::Rc<core::cell::RefCell<Box<Employee + 'static>>>`,
found `alloc::rc::Rc<core::cell::RefCell<Box<Human>>>`
(expected trait Employee,
found struct `Human`) [E0308]
src/main.rs:44 my_employee: human2.clone(), // This doesn't work
^~~~~~~~~~~~~~
What's the right approach in this situation?
Disclaimer: in this answer I will assume that you are willingly NOT using an enum because you want Employee to be open.
This issue comes up in about every single language that uses dynamic polymorphism, and the traditional answer is the Visitor Pattern.
It is not exactly ideal, though, because of the dependencies it introduces, so if necessary you can use the Acyclic Visitor Pattern; however I advise that you start with a bare bone visitor before delving further.
trait EmployeeVisitor {
fn visit_employee(&self, e: &Employee);
fn visit_human(&self, h: &Human);
}
trait Employee {
fn accept(&self, v: &EmployeeVisitor) {
v.visit_employee(self);
}
}
impl Employee for Human {
fn accept(&self, v: &EmployeeVisitor) {
v.visit_human(self);
}
}
This is the traditional "every problem can be solved with a layer of indirection", and it incurs the traditional issue of bringing another layer of indirection.
Note: This was asked about a pre-1.0 version of Rust, involving a feature which has since been withdrawn from the language.
I am using the experimental feature feature(struct_inherit).
I have the LocalPlayer and NetPlayer structs and they both implement the Inputable trait as well as inherit the player_num field from the virtual struct Player. Depending on how a game starts, player_2 in my program can either be a LocalPlayer or NetPlayer. Depending on which one, the way that the Inputable trait is implemented changes.
The compiler won't let me dynamically assign player_2 a type depending on whether it's a NetPlayer or LocalPlayer. It complains:
error: mismatched types: expected ~player::LocalPlayer but found ~player::NetPlayer (expected struct player::LocalPlayer but found struct player::NetPlayer)
It also won't let me typecast NetPlayer or LocalPlayer pointers to a Player pointer. It claims they aren't scalable.
The code sample in question is as follows:
let player1 = box player::LocalPlayer::new(0);
let player2;
if local {
player2 = box player::LocalPlayer::new(1);
} else if hosting {
player2 = box player::NetPlayer::new(1);
player2.host();
} else {
player2 = box player::NetPlayer::new(1);
player2.connect();
}
/* ... Omitted Code ... */
let input = player2.get_input(); // Trait function
The struct implementations are as follows:
pub virtual struct Player {
player_num: uint
}
pub struct LocalPlayer: Player;
pub struct NetPlayer: Player;
Struct inheritance in Rust is very primitive; you probably don't want to use it. There is no subtyping or coercion between inheriting structs in Rust. The feature basically just allows you to save typing if you have a lot of structs with similar fields (it's also future proofing for maybe adding more features around them in the future).
You could make Player, LocalPlayer and NetPlayer traits. You then get the subtyping behaviour you want between them. Alternatively, you could make just LocalPlayer and NetPlayer structs and let Player be a trait. You could even have a PlayerImpl struct which you could inherit from the way you do currently (if you have a lot of fields to share), but you would need to write independent impls for both structs.
I ended up implementing Player as an enum:
pub enum Player {
Net(NetPlayer),
Local(LocalPlayer)
}
Every time I call a shared function, I need to do the following:
let input = match player2 {
player::Net(player) => player.get_input(),
player::Local(player) => player.get_input(),
};
Rust's traits are similar to interfaces, and can be composed to emulate a hierarchy of interfaces. They're often an alternative to inheritance:
trait GetInput {
fn get_input(&self);
}
impl GetInput for Player {
fn get_input(&self) {}
}
impl GetInput for NetPlayer {
fn get_input(&self) {}
}
// Fast: interface selected at compile time
fn do_something<T: GetInput>(player: T) {
player.get_input();
}
// Flexible: interface selected at run time (virtual call)
// Use with Box<Player> (see "Trait Objects")
fn do_something_dyn(player: &dyn GetInput) {
player.get_input();
}
However, Rust doesn't have inheritance of data. To have common fields shared between types, you need some alternative DIY solution (e.g. getters/setters in traits or structs with enums).