Conflicting impls but not... is this specialisation?

Hello! Is this possible to express at all, or only on nightly? I'm happy to achieve the same outcome if anyone knows a different (perhaps more boilerplatey) way to do so?

pub trait ToRenderedText<T> {
    fn to_rendered_text(&self) -> String;
}

impl<T: HasWord + HasRect> ToRenderedText<T> for [T] {
    fn to_rendered_text(&self) -> String {
        WordsToRenderedText::to_rendered_text(&self)
    }
}

impl<T: HasChar> ToRenderedText<T> for [T] {
    fn to_rendered_text(&self) -> String {
        CharsToRenderedText::to_rendered_text(&self)
    }
}
error[E0119]: conflicting implementations of trait `ToRenderedText<_>` for type `[_]`
  --> render.rs:23:1
   |
11 | impl<T: HasWord + HasRect> ToRenderedText<T> for [T] {
   | ---------------------------------------------------- first implementation here
...
23 | impl<T: HasChar> ToRenderedText<T> for [T] {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[_]`

Which implementation shoulb be picked if T implements all the traits in the bounds?

1 Like

There is no way to express the idea that a single type cannot implement all of HasChar, HasWord, and HasRect. On nightly, negative impls may allow you to do this but I don't know how complete that feature is.

1 Like

ah, i see... there is a universe where a T happens to impl all three traits, and then the compiler wouldn't know what to do?

I found this blog post about specialisation on stable: Generalized Autoref-Based Specialization · Lukasʼ Blog

but unfortunately I don't quite grasp it. Would this allow me to achieve the desired results? I don't mind if it is hacky, the code would be confined to a single module.

If you don't mind boilerplatey, perhaps something like the following could work?

trait HasWord {}
trait HasRect {}
trait HasChar {}

trait Dispatch {
    const HAS_WORD: bool;
    const HAS_RECT: bool;
    const HAS_CHAR: bool;
    fn as_has_word(&self) -> &dyn HasWord;
    fn as_has_rect(&self) -> &dyn HasRect;
    fn as_has_char(&self) -> &dyn HasChar;
}

pub trait ToRenderedText<T> {
    fn to_rendered_text(&self) -> String;
}

impl<T: Dispatch> ToRenderedText<T> for [T] {
    fn to_rendered_text(&self) -> String {
        if <T as Dispatch>::HAS_WORD && <T as Dispatch>::HAS_RECT {
            todo!()
        } else if <T as Dispatch>::HAS_CHAR {
            todo!()
        } else {
            panic!()
        }
    }
}

(Playground)

Methods as_has_… could have a panic() implementation where the trait isn't implemented.


Here a working toy example:

trait TrA {
    fn foo(&self);
}

trait TrB {
    fn bar(&self);
}

trait TrAB {
    const A: bool = false;
    const B: bool = false;
    fn as_a(&self) -> &dyn TrA {
        panic!()
    }
    fn as_b(&self) -> &dyn TrB {
        panic!()
    }
}

struct S1;
struct S2;

impl TrA for S1 {
    fn foo(&self) {
        println!("Running foo on S1");
    }
}

impl TrAB for S1 {
    const A: bool = true;
    fn as_a(&self) -> &dyn TrA {
        self
    }
}

impl TrB for S2 {
    fn bar(&self) {
        println!("Running bar on S2");
    }
}

impl TrAB for S2 {
    const B: bool = true;
    fn as_b(&self) -> &dyn TrB {
        self
    }
}

fn generic_on_a_and_b<T: TrAB>(x: T) {
    if <T as TrAB>::A == true {
        x.as_a().foo();
    } else if <T as TrAB>::B == true {
        x.as_b().bar();
    } else {
        panic!()
    }
}

fn main() {
    generic_on_a_and_b(S1);
    generic_on_a_and_b(S2);
}

(Playground)

Output:

Running foo on S1
Running bar on S2

It requires manual implementation of TrAB though.

It's not that there is, but that there may be. And if there is, the compiler can't go back and give you an error after the fact, so it has to do it upfront.

1 Like

Very interesting solution! Unfortunately i'm not able to get it to work in my version.

I don't think i can use dyn because you can not use mutliple traits in a dyn,

So i tried trait HasWordAndRect: HasWord + HasWordAndRect {}

but &[dyn HasWordAndRect] its size is not known at compile time.

Can you see a possibility here:

pub trait ToRenderedText<T> {
    fn to_rendered_text(&self) -> String;
}

trait Dispatch {
    const CHAR_POSITIONS: bool;
    const TEXT_POSITIONS: bool;
    fn as_char_positions(&self) -> &[CCharPosition];
    fn as_text_position(&self) -> &[CTextPosition];
}

impl<T: Dispatch> ToRenderedText<T> for [T] {
    fn to_rendered_text(&self) -> String {
        if <T as Dispatch>::CHAR_POSITIONS {
            // CCharPosition impls HasChar!
            //
            // impl<T: HasChar> CharsToRenderedText<T> for [T] {
            //     fn to_rendered_text(&self) -> String {
            //         to_rendered_text(self)
            //     }
            // }
            //
            // impl<T: HasChar + HasRect> CharsToRenderedText<T> for &[T] {
            //     fn to_rendered_text(&self) -> String {
            //         to_rendered_text(self)
            //     }
            // }
            CharsToRenderedText::to_rendered_text(<T as Dispatch>::as_char_positions(
                self, /* expected &T, found &[T] */
            ))
        } else if <T as Dispatch>::TEXT_POSITIONS {
            // CTextPosition impls HasWord + HasRect!
            //
            // impl<T: HasWord + HasRect> WordsToRenderedText<T> for [T] {
            //     fn to_rendered_text(&self) -> String {
            //         to_rendered_text(self)
            //     }
            // }
            //
            // impl<T: HasWord + HasRect> WordsToRenderedText<T> for &[T] {
            //     fn to_rendered_text(&self) -> String {
            //         to_rendered_text(self)
            //     }
            // }
            WordsToRenderedText::to_rendered_text(<T as Dispatch>::as_text_position(
                self, /* expected &T, found &[T] */
            ))
        } else {
            unreachable!()
        }
    }
}

impl Dispatch for &[CCharPosition] {
    const CHAR_POSITIONS: bool = true;
    const TEXT_POSITIONS: bool = false;
    fn as_char_positions(&self) -> &[CCharPosition] {
        *self
    }
    fn as_text_position(&self) -> &[CTextPosition] {
        panic!()
    }
}

impl Dispatch for &[CTextPosition] {
    const CHAR_POSITIONS: bool = false;
    const TEXT_POSITIONS: bool = true;
    fn as_char_positions(&self) -> &[CCharPosition] {
        panic!()
    }
    fn as_text_position(&self) -> &[CTextPosition] {
        *self
    }
}
  --> cabinet_document/src/analysis/text/render.rs:21:85
   |
16 | impl<T: Dispatch> ToRenderedText<T> for [T] {
   |      - this type parameter
...
21 |             WordsToRenderedText::to_rendered_text(<T as Dispatch>::as_text_position(self))
   |                                                   --------------------------------- ^^^^ expected type parameter `T`, found slice
   |                                                   |
   |                                                   arguments to this function are incorrect
   |
   = note: expected reference `&T`
              found reference `&[T]`

It's difficult to work with the example if I don't have at least dummy types, as the example won't compile on Playground, for example. Maybe you could provide a minimal example on Playground?

1 Like

Sure!! Thanks for the help!! Rust Playground

Thanks also for making a Playground example. This is as far as I got: Playground. But not sure if I understand the example enough to resolve the problems (and if I fixed the errors in the right way).

yo! you were so close! i just needed to hide the other traits away, so that it wouldn't be ambiguous which trait to use!
:smiley: Rust Playground

Nice that it works. I'm just not sure if it's a "good" / idiomatic solution, or if there's some better way.

I noticed that the deref operation using &* in your Dispatch implementations is actually a no-op (because self has type &Self already and you return a &Self, and the impl<T: ?Sized> Deref for &T is a no-op). So you can simplify:

 impl Dispatch for [CCharPosition] {
     const CHAR_POSITIONS: bool = true;
     const TEXT_POSITIONS: bool = false;
     fn as_char_positions(&self) -> &[CCharPosition] {
-        &*self
+        self
     }
     fn as_text_position(&self) -> &[CTextPosition] {
         panic!()
     }
 }
 
 impl Dispatch for [CTextPosition] {
     const CHAR_POSITIONS: bool = false;
     const TEXT_POSITIONS: bool = true;
     fn as_char_positions(&self) -> &[CCharPosition] {
         panic!()
     }
     fn as_text_position(&self) -> &[CTextPosition] {
-        &*self
+        self
     }
 }

(Playground)

Moreover, not sure if it perhaps could be a good idea to provide default implementations and default boolean values for the Dispatch trait.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.