This is more of a code-readability question than anything.
I have a trait implemented on a slice
impl Key for &[u8] {}
Thanks to const generics, I could implement the trait on a reference to a fixed array:
impl <const ARRAY_SIZE : usize>Key for &[u8; ARRAY_SIZE] {}
but that implementation becomes problematic when I have methods that return Self. The trait makes sense for a slice, and just doesn't make sense for an array.
So, what I really want is for the compiler to automatically borrow a slice from the array when I call a function that takes a generic trait T as an arg. So the function calls would look like this:
The problem is that the Rust doesn't perform any automatic type conversion on generic function call, as it would makes adding impl for existing trait on existing type a breaking change, which is not desirable. Possible workaround is to make another trait, say IntoKey, to handle manual conversion.
trait Key { ... }
trait IntoKey {
type Key: Key;
fn into_key(self) -> Self::Key;
}
impl<'a> Key for &'a [u8] { ... }
impl<K: Key> IntoKey for K {
type Key = Self;
fn into_key(self) -> Self::Key {
self
}
}
impl<'a, const SIZE: usize> IntoKey for &'a [u8; SIZE] {
type Key = &'a [u8];
fn into_key(self) -> Self::Key {
&self[..]
}
}
fn key_fn<K: IntoKey>(key: K) {
let key = key.into_key();
...
}
What you have written does indeed solve the problem as I asked it. I felt really dumb when I read your answer because I thought I had already tried exactly this before posting the question.
But I felt a little less dumb when I realized that having a generic type argument to the Key and IntoKey traits now makes the generic Key impl conflict with the array impl.
trait Key<KeyCharT> { }
trait IntoKey<KeyCharT> {
type Key: Key<KeyCharT>;
fn into_key(self) -> Self::Key;
}
impl<'a, KeyCharT> Key<KeyCharT> for &'a [KeyCharT] { }
impl<KeyCharT, K: Key<KeyCharT>> IntoKey<KeyCharT> for K {
type Key = Self;
fn into_key(self) -> Self::Key {
self
}
}
impl<'a, KeyCharT, const SIZE: usize> IntoKey<KeyCharT> for &'a [u8; SIZE] {
type Key = &'a [u8];
fn into_key(self) -> Self::Key {
&self[..]
}
}
fn key_fn<K: IntoKey<u8>>(key: K) {
let key = key.into_key();
}
Is there a way around this other than to mirror every impl of Key for IntoKey?
Does it make sense if a certain type has multiple Key impl with different KeyCharT parameter? If not, it would be better to make it as associate type instead of the generic parameter, which makes my example works mostly as-is(after fixing some typo :P).
I think this is the best explanation for the question: When should I use a generic trait parameter and when should I use an associated type?
This question previously confused me badly, so, if I may paraphrase what you said:
If impl Trait<TypeA> for T and impl Trait<TypeB> for T might coexist, then use a generic type parameter. But if the T in impl Trait for T implies the other type, then use an associated type.
It was a long and arduous process, but I reworked my whole project to transition the KeyCharT from a generic trait argument to an Associated Type, and now your solution works perfectly.
Was it worth it for the tiny improvement in readability? Debatable. Was it worth it to level-up my Rust understanding surrounding associated types? 100% Yes!
My use of the word "implies" was conceptual. Specifically, in my case, I had an Associated Type (KeyCharT), the value of which was implied by the type the trait was implemented on. So:
impl Key for &[u8] {
type KeyCharT = u8;
}
or
impl Key for Vec<u8> {
type KeyCharT = u8;
}
In my example, there can only be one reasonable value for KeyCharT and therefore:
impl Key<u8> for Vec<u8> {
}
is redundant and needlessly limiting. (This is the implementation I had when I asked the question)
But going for a more self-contained rule-of-thumb, It is basically what @Hyeonu wrote. So, I'll further paraphrase it as:
If impl Trait<TypeA> for T and impl Trait<TypeB> for T both make sense on their own and might coexist together, then use a generic type parameter. But if only one impl Trait for T makes sense for each T then use an associated type.