Associate type needs bounds, how to not make a mess?

I am watching at the example in Associated types - Rust By Example

and as soon as I change

first(&self) -> i32
to
first(&self) -> Self::Scalar

I suddenly need all sorts of bounds on the Contains::Scalar.
Whats the best way of specifying the bound std::ops::Sub and others?
I dont want the trait to know what operations Scalar can perform, is this possible?

// A trait which checks if 2 items are stored inside of container.
// Also retrieves first or last value.
trait Contains {
    type Scalar;

    fn contains(&self, _: &Self::Scalar, _: &Self::Scalar) -> bool;

    fn first(&self) -> i32;
    fn last(&self) -> i32;
}

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

fn main() {
}

You could add a second generic parameter to difference for the Scalar type and put a Sub bound on that:

use std::ops::Sub;

trait Contains {
    type Scalar;

    fn contains(&self, _: &Self::Scalar, _: &Self::Scalar) -> bool;

    fn first(&self) -> Self::Scalar;
    fn last(&self) -> Self::Scalar;
}

fn difference<C: Contains<Scalar = S>, S>(container: &C) -> i32
where
    S: Sub<S, Output = i32>,
{
    container.last() - container.first()
}

fn main() {}

Playground.

3 Likes

Another way to write the same bound on difference:

fn difference<C: Contains>(container: &C) -> i32
where
    // Sub has Rhs = Self by default
    C::Scalar: Sub<Output = i32>
{
    container.last() - container.first()
}

Or even using the recently stabilized "inline" associated type bounds:

fn difference<C>(container: &C) -> i32
where
    // Moved the entire bound to the where clause for readability
     C: Contains<Scalar: Sub<Output = i32>>
{
    container.last() - container.first()
}
4 Likes

Realistically, if difference has to return a i32, it probably makes sense to just bound it on C: Contains<Scalar = i32> unless you are dealing with some specific situation. i64 doesn't implement Sub<Output = i32>, for instance, so that wouldn't let you use other primitive integers.

You might want

fn difference<C, S>(container: &C) -> S
where
    C: Contains<Scalar = S>,
    S: Sub<Output = S>

(or something like that).

3 Likes

Crazy, that almost looks recursive.

Really cool solution, I did not know that is possible. Its a slight brainfuck, but actually the way it should be =), thanks for thinking further!