A complicated trait implementation

There are some simple traits:

trait A {
    type T;
}

trait B<T> {
    fn oo(&self);
}

impl<T> B<i32> for T 
where
    T: A<T = i32>
{
    fn oo(&self) {
        println!("from A<T = i32>");
    }
}

impl<T> B<f32> for T
where
    T: A<T = f32>
{
    fn oo(&self) {
        println!("from A<T = f32>");
    }
}

And I need to do the following:

trait C {
    fn fx(&self);
}

impl<T1, T2> C for T1
where
    T1: A<T = T2>,
    T2: /*
    To make Self has the method oo, it is needed that T2 has some constraint.
    In this context, T2 is any value of  type K, that B<K> is implemented on A<K>
    because B<f32> is implemented on A<T = f32> and
            B<i32> is implemented on A<T = i32>,
    So T2 must b something like B <: Union{i32, f32}
     */
{
    fn fx(&self) {
        println!("start...");
        self.oo()
    }
}

I do not know how to write it down.

I could get this:

impl<T1> C for T1
where
    T1: A<T = i32>,
{
    fn fx(&self) {
        println!("start...");
        self.oo()
    }
}

impl<T1> C for T1
where
    T1: A<T = f32>,
{
    fn fx(&self) {
        println!("start...");
        self.oo()
    }
}

But obviously, the code is duplicated. What should I do?

Can you give us meaningful names instead of A and B? It's really hard to grasp what you are trying to achieve. The terminology in the question also raises concerns (e.g. you say that B<K> is implemented on A<K> but A<K> is a trait, not a type, so this isn't possible). It would be easier to help you if you could state what the higher-level purpose of this is.

It is also likely that you are using too many generic parameters, or you are using generic parameters on traits where you should be using associated types. A good rule of thumb is:

  • if you want many different impls for the same trait for a type, make the trait generic;
  • if you want the trait impl to imply (i.e., functionally, uniquely determine) some other type, then use an associated type.

I don't understand the XY [1] either, but here's a single implementation of C that covers the example.

impl<T1, T2> C for T1
where
    T1: B<T2> + A<T = T2>,
{
    fn fx(&self) {
        println!("start...");
        self.oo()
    }
}

  1. or AB? ↩︎

2 Likes

Here is a more concrete example. I'm still new to Rust, so the code may be not easy to read. I tried to make it short, but it still remains long, so excuse me first.

use std::hash::{Hash, Hasher};
use std::fmt::Debug;
use std::collections::HashMap;

struct DataSave {
    save_i32: HashMap<Box<dyn Calc<Output = i32>>, i32>,
    save_string: HashMap<Box<dyn Calc<Output = String>>, String>,
}

trait Calc: CalcId {
    type Output;
    fn check_the_calc(&self, data_save: &DataSave);
    fn to_box(&self) -> Box<dyn Calc<Output = Self::Output>>
    where 
        Self: Clone + 'static,
    {
        Box::new(self.clone())
    }
}


trait ContainedIn<T> {
    fn contained_in(&self, data_save: &DataSave) -> bool;
}


impl<T> ContainedIn<i32> for T
where 
    T: Calc<Output = i32> + Clone + 'static,
{
    fn contained_in(&self, data_save: &DataSave) -> bool {
        data_save.save_i32.contains_key(&self.to_box())
    }
}

impl<T> ContainedIn<String> for T
where
    T: Calc<Output = String> + Clone + 'static,
{
    fn contained_in(&self, data_save: &DataSave) -> bool {
        data_save.save_string.contains_key(&self.to_box())
    }
}

impl DataSave {
    fn contains_key<T: ContainedIn<N>, N>(&self, k: &T) -> bool {
        k.contained_in(self)
    }
}

// this part is just for a Box<dyn Calc<Output = T>> can be contained in a HashMap as key
// may be can be ignored
trait CalcId {
    fn id(&self) -> String;
}


impl<T> CalcId for T
where
    T: Calc + Debug,
{
    fn id(&self) -> String {
        format!("{:?}", self)
    }
}

impl<T> Hash for Box<dyn Calc<Output = T>> {
    fn hash<H>(&self, state: &mut H) where H: Hasher {
        self.id().hash(state)
    }
}

impl<T> PartialEq for Box<dyn Calc<Output = T>> {
    fn eq(&self, other: &Box<dyn Calc<Output = T>>) -> bool {
        self.id() == other.id()
    }
}

impl<T> Eq for Box<dyn Calc<Output = T>> {}
//--------------------------------------------------------------------------------

Any type implemented Calc has a method check_chan_calc, which takes a paramter DataSave. What the method do is shown on next block code.

And the DataSave has a method contains_key, which take a type implemented on ContainedIn, and return a bool value. So this contains_key just work like the same function in HashMap.

Then I create some types:

#[derive(Debug, Clone, PartialEq, Eq)]
struct OutI32;
#[derive(Debug, Clone, PartialEq, Eq)]
struct OutString;

trait DoHaveyCalc {
    type Output;
    fn do_havey_calc(&self) -> Self::Output;
}

impl DoHaveyCalc for OutI32 {
    type Output = i32;
    fn do_havey_calc(&self) -> Self::Output {
        1
    }
}

impl DoHaveyCalc for OutString {
    type Output = String;
    fn do_havey_calc(&self) -> Self::Output {
        String::from("a")
    }
}

impl<T, N, K> Calc for T
where
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static,
    T: ContainedIn<K>
    /*
    I tried this, but doese not work:
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static + Contained<K>,
     */
{
    type Output = i32;
    fn check_the_calc(&self, data_save: &DataSave) {
        if data_save.contains_key(self) {
            println!("self in data_save");
        } else {
            println!("self not in data_save");
        }
    }
}

Finnally, I try to implement Calc:

impl<T> Calc for T
where 
    T: DoHaveyCalc<Output = i32> + Debug + Clone + 'static,
{
    type Output = i32;
    fn check_the_calc(&self, data_save: &DataSave) {
        if data_save.contains_key(self) {
            println!("self in data_save");
        } else {
            println!("self not in data_save");
        }
    }
}

let data_save = DataSave { save_i32: HashMap::new(), save_string: HashMap::new() };
OutI32.check_the_calc(&data_save)

This works, but I can just implement Calc on DoHaveyCalc<Output = i32>. I also need to implement Calc on DoHaveyCalc<Output = String>. This is the part I have no idea how to do it.
The code I imagined may look like:

impl<T, N, K> Calc for T
where
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static,
    T: ContainedIn<K>
    /*
    I tried this, but doese not work:
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static + Contained<K>,
     */
{
    type Output = i32;
    fn check_the_calc(&self, data_save: &DataSave) {
        if data_save.contains_key(self) {
            println!("self in data_save");
        } else {
            println!("self not in data_save");
        }
    }
}

I tried this, but this doesn't work. I thought it may be a circal trait constraint or something, I don't know.
I gived a more concret example.

The general rule is that a trait implementation must be findable from the trait and the implementor alone. If I give you a T and ask "does it implement Calc", given the implementation header

impl<T, N, K> Calc for T
where
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static,
    T: ContainedIn<K>

You can't answer the question unless I also tell you K (the answer depends on if T: ContainedIn<K> or not). That's the root of the problem with K being unconstrained.


The only reason you have a type parameter on this one:

trait ContainedIn<T> {
    fn contained_in(&self, data_save: &DataSave) -> bool;
}

Is, I presume, so you can have multiple implementations here:

impl<T> ContainedIn<i32> for T where T: Calc<Output = i32> + Clone + 'static,
impl<T> ContainedIn<String> for T where T: Calc<Output = String> + Clone + 'static,

And cloning and boxing just to check a hash and then throwing it all away isn't great either. You could just have

trait ContainedIn {
    fn contained_in(&self, data_save: &DataSave) -> bool;
}

impl ContainedIn for Box<dyn Calc<Output = i32>> {
    fn contained_in(&self, data_save: &DataSave) -> bool {
        data_save.save_i32.contains_key(self)
    }
}

And that's enough to get rid of the unused parameter, which is also the unconstrained parameter in your later implementation.

Additionally, you can get rid of the boxes too, if you change your Hash and PartialEq implementations (or just add more implementations).

So this compiles, perhaps an answer to your OP.


Side note: This is a bad idea, you can't rely on Debug output being stable or, sometimes, even useful. If this is all internal it may be sufficient (though Display would be better), although it's more or less what I think of as "using generics as macros". [1]

impl<T> CalcId for T where T: Calc + Debug,
// The ID for hasing and equality is the debug output

A more robust approach would be a dyn PartialEq and dyn Hash approach, but, I'll just end this post here.


  1. Sometimes a symptom of a larger "concrete abstraction" situation: You're adding a lot of generic framework for what is (in the example) just two underlying types. ↩︎

2 Likes

I append a main function on:

and get this but it does not work.

I try to understand how implemeting trait work:
Give me a implemention header:

impl<T, N> Calc for T
where
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static,
    T: ContainedIn

and the type:

OutI32

I need to check "Dose OutI32 implement Calc". I know OutI32: DoHaveyCalc<Output = N> + Debug + Clone + 'static. But for now, before this checking finish, OutI32 does not implement ContainedIn yet. So OutI32 does not implement Calc.
But if I could do this: when I am checking " Does OutI32 implement ContainedIn ", I need to know "Does OutI32 implement Calc", but I am checking "Dose OutI32 implement Calc". If I pretend "OutI32 implement Calc", then the checking of " Does OutI32 implement ContainedIn " pass, then the checking of "Dose OutI32 implement Calc" pass. And I can say "OutI32" implement Calc.
But the playground above seems to say my first understanding makes more sendse.

I anticipated the need to cast to dyn Calc, but I must have missed something else, my apologies. I'll give it another look when I have some free time.

OK, I just started over instead of looking at my own last attempt. Here's the result.

First I made the implementations on dyn instead of Box<dyn> again.

Next, ContainedIn seems inside-out to me: DataSave is the thing that should know what it contains, so that should be the Self. And indeed, the method body looks at what I presume to be private fields of DataSave. So I created this trait to provide DataSave::contains_key instead:

trait Contains<CalcOutput> {
    fn contains_key<C: Calc<Output = CalcOutput>>(&self, calc: &C) -> bool;
}
impl Contains<i32> for DataSave { ... }
impl Contains<String> for DataSave { ... }

And then had ContainedIn (with no type parameter) rely on that (although it's unused):

// Just for method calling convenience?
trait ContainedIn {
    fn contained_in(&self, data_save: &DataSave) -> bool;
}
impl<C, Output> ContainedIn for C
where
    C: Calc<Output = Output>,
    DataSave: Contains<C::Output>,
{ ... }

And the blanket implementation you're asking about similarly becomes

impl<T, N> Calc for T
where
    T: DoHaveyCalc<Output = N> + Debug + Clone + 'static,
    DataSave: Contains<N>
{
    type Output = N; // n.b. you had this as `i32` in your generic attempt
    // ...

Does this fit your needs?

Playground.

1 Like

Yes, it does, thanks very much.

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.