arifd
March 27, 2023, 11:32am
1
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
arifd
March 27, 2023, 11:43am
4
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?
arifd
March 27, 2023, 12:19pm
5
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.
jbe
March 27, 2023, 12:59pm
6
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.
H2CO3
March 27, 2023, 1:21pm
7
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
arifd
March 27, 2023, 2:16pm
8
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]`
jbe
March 27, 2023, 2:23pm
9
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
arifd
March 27, 2023, 2:41pm
10
Sure!! Thanks for the help!! Rust Playground
jbe
March 27, 2023, 2:58pm
11
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).
arifd
March 27, 2023, 3:41pm
12
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!
Rust Playground
jbe
March 27, 2023, 8:08pm
13
Nice that it works. I'm just not sure if it's a "good" / idiomatic solution, or if there's some better way.
jbe
March 28, 2023, 1:20pm
14
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.