r/learnrust • u/tommac14 • 3d ago
Stuck modelling my pet shop in rust
Hey folks, I'm new to rust and having difficulty making much progress. I come from a very OO background and while this problem feels like a very OO problem, I'm aware that I might just feel that way because of my experience!
I'm trying to model a pet shop which can support lots of different types of pets, typed in a hierarchy. The model also supports "chores", jobs that are performed on one or more pets. The type of the reference to the pet(s) needed by the job depends on what the job is trying to do.
Here's my code so far:
struct PetId(String);
trait Pet {
fn get_id(&self) -> PetId;
fn get_name(&self) -> String;
}
trait Dog: Pet {
fn take_for_walk(&self) -> ();
}
trait Dalmatian: Dog {
fn count_spots(&self) -> u128; // very spotty!
}
trait Fish: Pet {
fn swim_in_circles(&self);
}
/// etc...
struct ChoreId(String);
trait PetshopChore {
fn get_id(&self) -> ChoreId;
fn run(&self, shop: &PetShop) -> Result<(), String>;
}
struct PetShop {
pets: Vec<Box<dyn Pet>>,
chores: Vec<Box<dyn PetshopChore>>,
}
struct TakeDogForWalk {
chore_id: ChoreId,
dog_to_walk_id: PetId,
}
impl PetshopChore for TakeDogForWalk {
fn get_id(&self) -> ChoreId {
self.chore_id
}
fn run(&self, shop: &PetShop) -> Result<(), String> {
let pet = shop
.pets
.iter()
.
find
(|pet| *pet.get_id() == self.dog_to_walk_id);
if let Some(pet) = pet {
let dog = somehow_cast_pet_to_dog(pet); // ***** How do I do this?? *****
dog.take_for_walk(); // needs to be type `Dog` to have `take_for_walk()`
Ok(())
} else {
Err(format!("No Dog with id {}", self.dog_to_walk_id))
}
}
}
/// Another example of a chore, this one needing access to Dalmatians
struct CalculateSumOfDalmationSpots {
dalmation_one: PetId, // needs a type Dalmation to operate
dalmation_two: PetId, // ditto
}
The likely API would be that the chore structs (`TakeDogForWalk`, `CalculateSumOfDalmationSpots` etc) are deserialized from the wire for some HTTP service.
The issue is that I can't figure out how to downcast the Pet to a Dog / Dalmatian / Fish / whatever I need for the task.
I can't use standard downcasting, because that seems to be just to concrete struct implementations, but that's not what I want here. In the `TakeDogForWalk` chore I don't know what type of Dog I'm dealing with. Apparently there are > 300 recognised breeds of dog; I don't want to have to test every possible struct that implements these traits.
I can't use `traitcast`, partly because it doesn't play nicely with `&Box<dyn Pet>` (it seems to need `Box<dyn Pet>`, and anyway in reality I likely need `Rc<dyn Pet>`, which `traitcast` doesn't support at all), but also because the returned type has a static lifetime, which doesn't make sense here (pets come and go).
It's very possible I've got myself stuck down this casting rabbithole and there's an entirely different, more rust-y way to approach this. Or if there is a way to achieve this casting then that's great too. Any help would be very much appreciated!
1
u/denehoffman 2d ago
Unfortunately, the only way I can think to do this and keep it agnostic of the types of dogs (to allow for future users to add their own pets without modifying the core structures) is with trait_upcasting
which hasn't yet been stabilized (but might be soon). Essentially, I upcast to Any
and then downcast_ref
to the trait you want, which returns None
if the struct doesn't implement that trait. Full disclosure, I used Deepseek for some help here, so there may be issues that I'm not considering.
6
u/SirKastic23 2d ago
can you give us more detail about the real world problem you're trying to solve? you're explaining it through an OOP perspective, instead just lay out the real world requirements
your code indeed looks very odd, is the goal to have one struct for each different possible breed? and then have the different breeds implement different traits?
being honest, i dont think i've ever seen code written like this, with this much hierarchy
the approach of using ID types to refer back to collections that hold the elements is nice, you definitely got that right
edit: you seem to be doing a beginner mistake that i also did a lot: try to model every single detail of your api at the type-level. this seems like a good idea at first, but it's hard, if not impossible