Could a dyn type have more traits?

i was tring to store some

Box<dyn Trait>

into a HashSet, but it is impossible, Cuz the things that can be inserted in HashSet should have some other trait like Hash + Eq ... but i can't impl those trait for Box here's the problem link.

I wish i could write some thing like Box<dyn MyTrait : Hash + Eq + 'static> or Box<dyn MyTrait + Hash + Eq + 'static>,which means the different types that impl MyTrait, but they all need to impl Hash + Eq first, so that i can insert them into a HashSet.

could it happens?

if this question is stupid, please don't laugh at me, and i will appriciate it if u tell me why it's impossible.

Thx for read, and may u have a nice day!

The problem at play is object safety. Types implementing Hash and Eq cannot be made object safe.

It is perfectly fine to use a trait object where its super trait is required: Rust Playground

But you cannot even create a trait object if the super trait is not object safe: Rust Playground

I would personally love if trait objects were more flexible. But I don't know the state of object safety improvements (if any) nor any recent research or activity on the topic.

Just allowing dyn Trait1 + Trait2 has its own implementation problems (see for example Where's the catch with Box<Read + Write>? and Sieve-tables for multiple traits objects (Box<A+B+C> to Box<A+C>, #2035)) except when the additional traits are autotraits, e.g. Send or Sync, for which this is already possible.

The alternative for now is to create a new trait with all the bounds with a blanked implementation. So instead of Box<dyn Trait1 + Trait2> you would have Box<dyn CombinedTrait> where CombinedTrait is defined as follows:

trait CombinedTrait : Trait1 + Trait2 {}
impl<T: Trait1 + Trait2 + ?Sized> CombinedTrait for T {}

Even then though it wouldn't work for your particular case because neither Hash nor Eq are object safe traits. For Hash this is because it contains generic functions, which correspond to infinite concrete functions, so they cannot be included in the trait object vtable. For Eq this is because types implementing it expect to only ever be compared with other instances of the same type, but with dyn Eq you could compare a type with a completly different one.

I don't think they will ever become object safe (maybe Hash, but definitely not Eq), so you have to solve this manually. @quinedot already showed how make object-safe variants of them so that you can implement Hash and Eq for dyn YourTrait, and that's the correct solution:

You should implement them for dyn YourTrait because it's a local type in your crate, then Box will automatically implement them whent he inner type implements them.

1 Like

One can use the object_safe crate as a workaround to that. More specifically, Box<dyn object_safe::HashObj> works as Box<dyn Hash> should (and it implements Hash). I think the proper fix for that will come when Rust supports unsized rvalues.

Now, to get a trait object that impls Hash and Eq at the same time, one would need to define a new trait, call it trait EqHashObj: object_safe::HashObj + object_safe::EqObj and then add impls impl Hash for dyn EqHashObj and impl Eq for dyn EqHashObj which just converts into the superclass and calls the proper method (I'm not sure if this requires trait upcasting or if it can be done without it). And I'm not sure either, but may be the multi_trait_object crate could help with this.

The proper fix for the last part will require something like dyn Trait1 + Trait2, which I've seen discussed a number of times here but is always stumped by things like vtable layout.

I get what you are saying, but it sounds mechanically different to trait EqHashObj: Hash + Eq where EqHashObj can somehow magically be made object safe.

My point is that Rust already supports the case with trait ReadWrite: Read + Write where you can pass a ReadWrite trait object to functions and types that receive T: Read + Write.

After playing with object-safe, it became apparent that you can create BTreeSet<Box<dyn EqHashObj>>, you just can't add anything to it without an OrdObj trait [1].

use object_safe::{impl_eq, impl_hash, impl_partial_eq, EqObj, HashObj};
use std::collections::BTreeSet;
use std::ops::Deref;

trait EqHashObj: EqObj + HashObj {}
impl<T: EqObj + HashObj> EqHashObj for T {}
impl_eq!(dyn EqHashObj);
impl_hash!(dyn EqHashObj);
impl_partial_eq!(dyn EqHashObj);

fn main() {
    // Creating the type works ...
    let mut x: BTreeSet<Box<dyn EqHashObj>> = BTreeSet::new();

    // ERROR: the trait `Ord` is not implemented for `(dyn EqHashObj + 'static)`,
    // which is required by `Box<(dyn EqHashObj + 'static)>: Ord`
    x.insert(Box::new(123_i32));
    x.insert(Box::new(456_u16));
}

  1. And I don't know if it's possible to create this trait and the necessary impl macros. This crate is a funny workaround for a big problem, though. ↩ī¸Ž

You can create any BTreeSet<T> with BTreeSet::new() as it has no requirement on T other than Sized. Any meaningful method however will require Ord however.

Such trait would be very similar to EqObj. Like EqObj the "hard" part is handling the case where you're comparing two different types. with EqObj the answer is just making them different, but with OrdObj you have to give a total order, so you must make one smaller than the other. A simple solution would be comparing their TypeId first. Something like this for example Rust Playground


I think this is becoming a discussion more fit for users.rust-lang.org at this point, wouldn't it be better to more there or in the original discussion (also there)?

My bad, I meant to use HashSet (clearly, as this is the type discussed in OP). s/BTreeSet/HashSet and it all works. My argument here is that super traits provide enough to cover OP's use case. It is object safety that gets in the way. dyn (Trait1 + Trait2) syntax sugar is a red herring.