How to specify associated types with same name in generics


#1

Hi all,

I am looking for some help on trait object, and ambiguous associated types.

I have a trait inheritting Index operator:

pub trait RevEntry { ... }

pub trait Revlog<'a>: Index<&'a NodeId> + Index<&'a isize> { ... }

And now I want to use a hashmp to hold the trait objects (I have to use dynamic dispatch in this situation):

pub revlogs: HashMap<PathBuf, Revlog<'a, Output = RevEntry>>,

The compiler clearly warns me about the ambiguity: error[E0221]: ambiguous associated type Output in bounds of revlog::Revlog<'a>. I tried several way to do fully qualified names, but my homemade guess doesn’t work.

pub revlogs: HashMap<PathBuf, rv::Revlog<'a, std::ops::Index<&'a isize>::Output = rv::RevEntry>>,
pub revlogs: HashMap<PathBuf, rv::Revlog<'a, <Self as std::ops::Index<&'a isize>>::Output = rv::RevEntry>>,

Could you help me with this? Thank you!


#2

First, you don’t have trait objects in the map there. You’d need to put a box around Revlog and RevEntry (if you intend to own the object).

Second, I’m not sure how you can disambiguate there. Typically you’d use a projection to get at an associated type of something generic (ie <T as SomeTrait>::Associated) but you need the starting T. Here there’s no real starting T, just the abstract Revlog trait. There’s no Self in scope either because we’re not “inside” the trait definition. Conceptually, it seems like it should be possible to say what you want here. Maybe there’s some syntax for it that someone knows. I suspect it’ll be a serious eyesore! :slight_smile:

Third, you’d need to specify both Output associated types here (if this is possible), one for each Index impl. Would they both be a RevEntry trait object?

Can you elaborate on why you want Revlog to require those Index impls? If there are certain places where you need indexing, perhaps you can require the Index impls just there?

I suspect there’s a way to redesign some of this to make things easier.


#3

Thank you, @vitalyd! I am working on implementing a program used to be in Python, so I mindset was not so right. For the first point, you are quite right, after putting on my static-lang hat, I figure that out. Thank you for bringing this up!

The original program can index the revlog by sequential number and SHA-1 hash, so I did use Index and Index. I re-design this part, as some other requirements, the ops overloading is not a good idea for me now.

I guess I dodged the second problem, I fail to solve this part.

Thank you!


#4

Ok, it’s good that you’re able to sidestep this issue. What you were trying to do is possible with generics by making use of the where clause. A contrived example (Revlog definition same as before):

fn foo<'a, R, E>(revlog: R)
where
    R: Revlog<'a>,
    E: RevEntry,
    R: Index<&'a isize, Output=E>,
    R: Index<&'a NodeId, Output=E>,
{}

You can do a similar thing for generic struct definitions or impl blocks because they support the where clause. But I don’t know how you’d declare a trait object similar to this.


#5

This is a good idea to use where in this way. I tried another way:

pub trait TestRevlog<'a>
    : Index<&'a NodeId, Output = RevEntry> + Index<&'a i32, Output = RevEntry> {
}

Sadly the HashMap part still asks for explicitly declare Ouput. I ran out of ideas. I am glad I listened to you and change my design.

Thank you!