Reputation: 4062
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:
IsEqual<T1, T2>::Output = True
, this implies that T1
and T2
are the same (not sure if it's supposed to and I can work around that)TypeNum::IsEqual
does not seem to be implemented for anything, at least not in a way my compiler can see.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
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:
Integer
using the Indexed
trait aboveIndexedEqual
above.This is probably a very bad idea, but it's here if you want it.
Upvotes: 0
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