Vector of trait objects with associated types

Hey everybody,

TLDR: I have a trait with an associated and several structs which implement it, and I try to have a vector of different objects from those structs. How do I either correctly type this or restructure my code in a better way?

I decided to participate in this years Advent Of Code problems and wanted to take the chance to learn Rust some more. Instead of having binaries for each day, I created a Solution trait with a solve function, and then for each day I can have a struct which implements said trait. Now all I need is a main function, set the day I wanna run via a const or console arg and that's it.

However, given that every day might have a different solution type, I added an associated type to the trait, and I figured as long as I can display the result, I don't care what each day computes. This creates some troubles with typing within the main function, when trying to group all the solutions together. I thought of either having a Vec of all solutions or using a match expression, but either way, I can't type these. Without the associated type I can use something like Box<dyn Solution>, but with it I don't know how to proceed.

Clarification: As far as I understand it from reading other posts and whatnot, this is not possible to type. Thus my question is not, How can I type this? but rather Where did my OOP-biased brain go wrong in structuring my code?.

It seemed only natural to me to implement it that way, but as I have learned so far, things sometimes require different approaches than what I'm used to. I tried going from the associated type to a generic trait, but that really just yields the same problem. By the way, I am aware that for my specific use case (Advent Of Code) I could discard the whole idea of using different result types, but I try to understand how my concept of structuring code translates from OOP code to Rust.

I created a playground to showcase my (non-compiling) code here: Rust Playground

Have a great day,
Plexian

The problem is that two trait objects with different associated types are not the same type (naturally). If you want to treat them uniformly, you have to use the same associated type, or just make the function return Box<dyn Display>. This compiles.

1 Like

Here's an explanation of why associated types must be specified for dyn Trait. Hopefully it at least helps in answering your question.

An enum is another alternative.

2 Likes

Thanks for your responses. I think I do understand why it doesn't work they way I want it to, but I'll read up on that.
I see your workarounds for that, and while they compile and work, they don't seem optimal to me. In either case I have to wrap the result into another type, which I expected to be done automatically by saying "it implements Display".

Does that not bother you? Having to write Box<dyn Display> and wrapping my result into that or into an Enum feels to me like something that can be abstracted instead of having to do it in every single Solution struct.

If not, is there maybe a different way you'd implement this? Given one binary and the option to execute any solution based on a variable input?

Not really. It's the combination of a few things.

But I probably didn't come from whatever OOP background you're coming from.[1]


  1. I'm not 100% convinced this is an OO mental mis-match, per se, might be some other aspects of your most familiar language(s) -- autoboxing and GC maybe. ↩ī¸Ž

1 Like

Not any more than gravity. It would be great if I could fly, but I can't because physics. "Physics" is also approximately the reason why what you want is impossible, so it's not a "language/designer is not smart enough" problem. You just have to deal with it.

Maybe you are thinking that performing the type erasure outside the solution and keeping the static associated types is more elegant. If that's so, I'm inclined to agree; in which case, do this.

If you want to treat the computation itself uniformly (i.e., not call all .solve() methods upfront), then again, that's impossible in a statically-typed language.

3 Likes

That seems like a good way to think about Rust then. While I understand that Rust is rather explicit, I'm not sure how I feel about the fact that that entails a lot of boilerplate. That's something I have to get used to, although I'm not saying that this a bad property, just counterintuitive for now.

That is what I was thinking, I'm glad you agree on that.


Nonetheless I appreciate the alternatives you presented, I'll have a look what fits the best.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.