Anyway, the trouble comes up because, now that I have this associated type instead of a concrete type, I lose all the impls on the type.
Once I step into the crazy world on the other side of the generic-types looking-glass, even the simplest bits of code seem to have a way of transforming into mind-bending puzzles.
trait Key<KeyCharT> : std::fmt::Display {
fn get_string(&self) -> String;
}
impl Key<char> for &str {
fn get_string(&self) -> String {
self.to_string()
}
}
impl Key<char> for String {
fn get_string(&self) -> String {
self.clone()
}
}
trait OwnedKey : Key<Self::KeyCharT> {
type KeyCharT;
fn from_key<K : Key<Self::KeyCharT>>(key : K) -> Self;
}
impl OwnedKey for String {
type KeyCharT = char;
fn from_key<K : Key<Self::KeyCharT>>(key : K) -> Self {
key.get_string()
}
}
//-- Yandros Type-Lifter Pattern --
trait ResolveKeyType {
type T : OwnedKey;
}
impl ResolveKeyType for Table<true> {
type T = String;
}
struct Table<const SOME_PARAM: bool>
where Self : ResolveKeyType,
{}
impl <const SOME_PARAM: bool>Table<SOME_PARAM>
where Self : ResolveKeyType
{
fn do_stuff() {
let owned_key = <Self as ResolveKeyType>::T::from_key("hello");
println!("{}", owned_key);
}
}
fn main() {
//Works.
let owned_key = String::from_key("hello");
println!("{}", owned_key);
//Doesn't Work
Table::<true>::do_stuff();
}
Do you think it is possible to keep the trait impls in the parts of the code where the type is unknown?
What do you wish was different? Implementing for Table<true> resolves the problem. (Plus fixing the signature to not take &self, which I see you've edited in your OP. Not sure if you changed anything else or if my comment still applies...)
I'm not sure I understand what you are saying. I want one implementation of Table::do_stuff() for any argument of SOME_PARAM. I don't want to move do_stuff() into a specific impl for just Table<true>
Are you saying that there is another way to implement Table<true> that enables the do_stuff() implementation for Table<SOME_PARAM> to compile?
The use of the word "Now" wasn't really a reference to chronology of my development, but more to the layering of the concepts in my understanding, and thus what I thought might be a logical order to express them in.
But you did make me realize that methods implemented in the ResolveKeyType trait may be a way to bridge this issue. Definitely not optimal, however.
I'm still at a loss for why the type resolution has no problem with the "Yandros Type-Lifter" by itself, and no problem with the "supertrait bounded by a subtrait based on an associated type", but can't handle the two together.
I guess, first order: what changes would make the original code compile without sacrificing the fact that do_stuff() is generic over any argument to SOME_PARAM.
Here, you impl Table<true> -- implementing for a concrete type. So within the implementation, the compiler can see that Self implements ResolveKeyType, that <Self as ResolveKeyType>::T is String, that String: OwnedKey, that <String as OwnedKey>::KeyCharT is char, and that &str: Key<char>.
In this version, your implementation is generic, but we now have a bound that Self: ResolveKeyType. This in turn requires <Self as ResolveKeyType>::T: OwnedKey, and the that trait has a method from_key which can convert any implementer of the Key trait to Self. And &str implements Key.
This seems "closer" to what you want, so let's see what changes you've introduced.
The Key trait now takes a type parameter
The OwnedKey trait now has an associated type, KeyCharT
OwnedKey::from_keyno longer accepts any Key implementer, and instead only accepts implementers of Key<Self::KeyCharT>
This last part introduces a restriction that didn't exist before. Before, any Key implementer would do, and &str was such an implementer. Now, it's no longer a given that &str is an implementer of Key<Self::KeyCharT>. Self::KeyCharT might be something other than char.
The error from the OP says:
the trait `Key<<<Table<SOME_PARAM> as ResolveKeyType>::T as OwnedKey>::KeyCharT>` is not implemented for `&str`
And the most direct way to get it to compile is to add that bound:
impl <const SOME_PARAM: bool> Table<SOME_PARAM>
where
Self: ResolveKeyType,
+ for<'a> &'a str: Key<<<Table<SOME_PARAM> as ResolveKeyType>::T as OwnedKey>::KeyCharT>,
{
// ...
But I wouldn't be surprised if there's a better way.
I was able to use a bound that was more appropriate to the actual situation in my real code. It won't make sense in terms of this toy example, but thank you for explaining what the compiler / type checker was doing.