0

I get error if use a generic realization for a generic structure. How should I explain to the compiler that the last function returns data which can be used in subtraction operation?

use std::ops::Sub;

struct Container<A, B>(A, B);

trait Contains {
    type A;
    type B;

    fn contains(&self, &Self::A, &Self::B) -> bool;
    fn first(&self) -> Self::A;
    fn last(&self) -> Self::B;
}

impl<C: PartialEq, D: PartialEq + Sub> Contains for Container<C, D> {
    type A = C;
    type B = D;

    fn contains(&self, number_1: &Self::A, number_2: &Self::B) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    fn first(&self) -> Self::A {
        self.0
    }
    fn last(&self) -> Self::B {
        self.1
    }
}

fn difference<C: Contains>(container: &C) -> i32 {
    container.last() - container.first()
}

fn main() {
    let number_1 = 3;
    let number_2 = 10;

    let container = Container(number_1, number_2);

    println!("Does container contain {} and {}: {}",
             &number_1,
             &number_2,
             container.contains(&number_1, &number_2));
    println!("First number: {}", container.first());
    println!("Last number: {}", container.last());

    println!("The difference is: {}", difference(&container));
}

I get an error:

error[E0369]: binary operation `-` cannot be applied to type `<C as Contains>::B`
  --> src/main.rs:30:5
   |
30 |     container.last() - container.first()
   |     ^^^^^^^^^^^^^^^^
   |
   = note: an implementation of `std::ops::Sub` might be missing for `<C as Contains>::B`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Highly related if not a duplicate [Requiring implementation of Mul in generic function](https://stackoverflow.com/q/29184358/155423) – Shepmaster Aug 17 '17 at 15:25

1 Answers1

2

This was a fun bit of type tetris to figure out :)

Someone else may be able to weigh in on a better way to implement what you're trying to do, but I can at least explain why your code isn't compiling.

There's four issues:

  • Your implementation of difference is generic over all implementations of Contains, therefore it's not enough to just put a constraint on Container's types - you need to put them on the trait itself as well.

  • Because you're trying to subtract an object of type Self::A from an object of type Self::B, you need to specify that in the constraint - it defaults to being Sub<Self>.

  • Rust won't implicitly convert the result of difference to an i32 - you either need to be generic over the return value of difference, or add an explicit conversion (which will involve adding more type constraints). I did the former, as it seems more in keeping with what you're trying to achieve.

  • first and last try to move ownership of self.0 and self.1 out of the struct - you need to either have them return borrows (which will involve lifetime shenanigans), or restrict Contains to only allow Copy types.

With those changes made, your code will look like this:

use std::ops::Sub;

struct Container<A, B>(A, B);

trait Contains {
    type A: Copy + PartialEq;
    type B: Copy + PartialEq + Sub<Self::A>;

    fn contains(&self, &Self::A, &Self::B) -> bool;
    fn first(&self) -> Self::A;
    fn last(&self) -> Self::B;
}

impl<C, D> Contains for Container<C, D>
where
    C: Copy + PartialEq,
    D: Copy + PartialEq + Sub<C>,
{
    type A = C;
    type B = D;

    fn contains(&self, number_1: &Self::A, number_2: &Self::B) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    fn first(&self) -> Self::A {
        self.0
    }
    fn last(&self) -> Self::B {
        self.1
    }
}

fn difference<C: Contains>(
    container: &C,
) -> <<C as Contains>::B as std::ops::Sub<<C as Contains>::A>>::Output {
    container.last() - container.first()
}

fn main() {
    let number_1 = 3;
    let number_2 = 10;

    let container = Container(number_1, number_2);

    println!(
        "Does container contain {} and {}: {}",
        &number_1,
        &number_2,
        container.contains(&number_1, &number_2)
    );
    println!("First number: {}", container.first());
    println!("Last number: {}", container.last());

    println!("The difference is: {}", difference(&container));
}

Which compiles and runs fine:

Does container contain 3 and 10: true
First number: 3
Last number: 10
The difference is: 7

I'd note that if Contains is always going to contain only numeric types, you'll probably be able to implement this far easier using the num crate, as shown in Trait for numeric functionality in Rust.

Joe Clay
  • 33,401
  • 4
  • 85
  • 85
  • Oh, thank you very much -:). When I can more read about that constraints which you have used in `associated types` and function `difference`? I read official docs but that syntax there isn't appeared. –  Aug 17 '17 at 19:35
  • @MrEfrem: I figured it out based off the Rust docs, the compiler errors and a bit of trial and error :p See [`Sub`](https://doc.rust-lang.org/std/ops/trait.Sub.html) and [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html). – Joe Clay Aug 18 '17 at 07:10