Edward Peters
Edward Peters

Reputation: 4062

Override trait implementation when two type parameters are the same?

I have a struct with two type parameters, and a trait:

struct MyStruct<T1, T2>{
    field1: T1,
    field2: T2,
}
trait ToList<T>{
    fn to_list(self) -> Vec<T>;
}

I want to implement my trait on this struct, but if the structs type parameters are equal, I have a "stronger" implementation I want to use:

impl <T> ToList<T> for MyStruct<T, T>
    {
    fn to_list(self) -> Vec<T>{
        vec![self.field1, self.field2]
    }
}
impl <T1, T2> ToList<T1> for MyStruct<T1, T2>
    {
    fn to_list(self) -> Vec<T1>{
        vec![self.field1]
    }
}

Note that the first implementation is only valid if the types are the same.

Either of the above implementations will compile, but if I try to define both, they conflict. I've been trying to follow the instructions from Use different impl when type parameters are the same, but that follows a much more complicated use case. Eventually I hit two walls:

use typenum::{IsEqual, True, False};

fn main() {
    let struct1 = MyStruct{field1: 1, field2: 2};
    let struct2 = MyStruct{field1: 1, field2: true};
    let list1  = struct1.to_list();
    let list2 = struct2.to_list();
    println!("{:?}", list1);
    println!("{:?}", list2);
}

struct MyStruct<T1, T2>{
    field1: T1,
    field2: T2,
}
trait ToList<T, AreEqual>{
    fn to_list(self) -> Vec<T>;
}

impl <T> ToList<T, True> for MyStruct<T, T> 
    {
    fn to_list(self) -> Vec<T>{
        vec![self.field1, self.field2]
    }
}
impl <T1, T2> ToList<T1, False> for MyStruct<T1, T2>
    where T2: IsEqual<T1, Output=False>,
    {
    fn to_list(self) -> Vec<T1>{
        vec![self.field1]
    }
}

This does not compile because with an error on the second to_list() call:

the method `to_list` exists for struct `MyStruct<{integer}, bool>`, but its trait bounds were not satisfied
method cannot be called on `MyStruct<{integer}, bool>` due to unsatisfied trait bounds
main.rs(12, 1): method `to_list` not found for this struct
main.rs(12, 1): doesn't satisfy `MyStruct<{integer}, bool>: ToList<{integer}, B0>`
main.rs(27, 27): the following trait bounds were not satisfied:
`<bool as IsEqual<{integer}>>::Output = B0`

I did eventually find that if I wrap bool and i32 in my own wrapper types and implement IsEqual on the wrappers I can make this compile, but to use that more broadly I'd have to write specific wrappers for every combination of types I intend to use.

Is there something I'm missing to make the typenum approach work, or is there an entirely different/simpler route I should be taking?

EDIT: The below shows how this can work if I provide an impl for the IsEqual trait. (I have to use Wrapped because I'm unable to implement it directly on externally defined types.)

use typenum::{IsEqual, True, False};

struct Wrapped<T>(T);

fn main() {
    let struct1 = MyStruct{field1: 1, field2: 2};
    let struct2: MyStruct<i32, bool>      = MyStruct{field1: 1, field2: true};
    let list1  = struct1.to_list();
    let list2 = struct2.to_list();
    println!("{:?}", list1);
    println!("{:?}", list2);
}

struct MyStruct<T1, T2>{
    field1: T1,
    field2: T2,
}
trait ToList<T, AreEqual>{
    fn to_list(self) -> Vec<T>;
}

impl <T> ToList<T, True> for MyStruct<T, T> 
    {
    fn to_list(self) -> Vec<T>{
        vec![self.field1, self.field2]
    }
}
impl <T1, T2> ToList<T1, False> for MyStruct<T1, T2>
    where Wrapped<T2>: IsEqual<Wrapped<T1>, Output=False>,
    {
    fn to_list(self) -> Vec<T1>{
        vec![self.field1]
    }
}

impl IsEqual<Wrapped<i32>> for Wrapped<bool>{
    type Output = False;
    fn is_equal(self, rhs: Wrapped<i32>) -> Self::Output {
        todo!()
    }
}
impl IsEqual<Wrapped<i32>> for Wrapped<i32>{
    type Output = True;
    fn is_equal(self, rhs: Wrapped<i32>) -> Self::Output {
        todo!()
    }
}

Upvotes: 1

Views: 101

Answers (2)

Edward Peters
Edward Peters

Reputation: 4062

So while @Chayim Friedman's answer is correct in general, I did find an extremely hacky partial workaround:

use typenum::*;

trait Indexed {
    type Index: Integer;
}
impl Indexed for bool {
    type Index = N1;
}
impl Indexed for usize {
    type Index = N10;
}

trait IndexedEqual<T1> {
    type IsEqual;
}
impl<T0, T1> IndexedEqual<T1> for T0
where
    T0: Indexed,
    T1: Indexed,
    <T0 as Indexed>::Index: IsEqual<<T1 as Indexed>::Index>,
{
    type IsEqual = <<T0 as Indexed>::Index as IsEqual<<T1 as Indexed>::Index>>::Output;
}

trait Specialized<T1, True> {
    fn print_sameness(&self, other: T1);
}
impl<T0, T1> Specialized<T1, True> for T0
where
    T0: IndexedEqual<T1, IsEqual = True>,
{
    fn print_sameness(&self, other: T1) {
        println!("They're the same");
    }
}
impl<T0, T1> Specialized<T1, False> for T0
where
    T0: IndexedEqual<T1, IsEqual = False>,
{
    fn print_sameness(&self, other: T1) {
        println!("They're different");
    }
}

fn main() {
    false.print_sameness(true);
    1.print_sameness(0);
    0.print_sameness(true);
    false.print_sameness(1);
}

Essentially, while the typenum crate isn't intended to provide specialization, it does provide a large number of types over which type-level equality checks can be performed. If you want to distinguish that certain types are the same or different, this can be achieved by:

  • Relating each type to an Integer using the Indexed trait above
  • Creating a specialized trait with a "Boolean" type parameter
  • Implementing the trait with different behavior and a different value of the tag, based on comparisons from IndexedEqual above.

This is probably a very bad idea, but it's here if you want it.

Upvotes: 0

Chayim Friedman
Chayim Friedman

Reputation: 70940

This is classic specialization, that Rust doesn't have, and even with specialization somewhere in the future stabilized (if ever), this case will probably be never stabilized, since it is unsound to specialize like that.

So no, you cannot do that in Rust.

Iff you don't need to work in generic code, but only when the concrete types are known, you can use the more limited autoref specialization.

Upvotes: 4

Related Questions