1

I've run into the problem of not being able to use where clauses in Boxes and other generic structs. This is useful for generic communicative adding. (A+B=B+A). An example of what I'm trying to achieve:

enum Foo<T> {
    Bar(Box<U where T: Add<U, Output = T>>),
    Baz(Box<U where T: Sub<U, Output = T>>),
}

This can be semi-done using where clauses in the type definition:

enum FooHack<T, A, S>
where
    A: Add<T, Output = T>,
    S: Sub<T, Output = T>,
{
    Bar(A),
    Baz(S),
    Dummy(PhantomData<T>), // to stop the compiler from complaining
}

The type signature for FooHack requires A and S be concrete types. Is there a strategy so that A and S can be generic?

Flarp
  • 139
  • 1
  • 10
  • 1
    `A` and `S` are already generic. You seem to want to use trait objects instead. – E_net4 Jan 03 '18 at 00:57
  • *The type signature for FooHack requires `A` and `S` be concrete types* — yes, that's kind of the *point* of a type being generic; you fill it in with concrete types when you go to use it. – Shepmaster Jan 03 '18 at 00:57
  • @E_net4 I do want to use trait objects, but I can't find a way to express it in the terms provided – Flarp Jan 03 '18 at 01:03
  • @Shepmaster You are correct, but what if I want to change Baz to something that also applies to `T: Sub` ? This would require a new type, I'm looking for a way to do it with trait objects. – Flarp Jan 03 '18 at 01:05
  • See also [How to Box a trait that has associated types?](https://stackoverflow.com/q/48027839/155423) – Shepmaster Jan 03 '18 at 01:14

1 Answers1

2

Perhaps you're already aware of trait objects? That's the exact match for your purpose to have U dynamically change for the fixed T.

However, two problems prevent the direct usage of Add or Sub as a trait object:

  1. Add and Sub aren't object-safe, as it expects a by-value self argument.
  2. Trait objects are existential over Self, but you want trait objects that are existential over RHS of Add/Sub.

Therefore you'll have to do extra work to introduce intermediate traits:

use std::ops::Add;

trait MyAdd<T> {
    fn my_add(&self, other: T) -> T;
}

impl<T, U> MyAdd<T> for U
where
    T: Add<U, Output = T>,
    U: Clone,
{
    fn my_add(&self, other: T) -> T {
        other + self.clone()
    }
}

enum Foo<T> {
    Bar(Box<MyAdd<T>>),
}

If you don't want U: Clone, there are options depending on your usage:

  • Assume T: Add<&'a U, Output = T> instead of T: Add<U, Output = T>
  • Use self: Box<Self> instead of &self in fn my_add

Edit: operator overloading

The proxy trait MyAdd<T> above doesn't proxy operator overloading by itself; thus you'll have to manually implement std::ops::Add for this.

// Coherence error
impl<T> Add<Box<MyAdd<T>>> for T {
    type Output = T;
    fn add(self, other: Box<MyAdd<T>>) -> Self::Output {
        other.my_add(self)
    }
}

However, this code doesn't work due to coherence restrictions. This restriction is fundamental because it's always possible that a sibling crate has an impl like impl<T> Add<T> for TypeDefinedInTheSiblingCrate, which surely overlaps with the impl above.

One possible workaround is to use the newtype pattern:

struct Wrapper<T>(T);

impl<T> Add<Box<MyAdd<T>>> for Wrapper<T> {
    type Output = Wrapper<T>;
    fn add(self, other: Box<MyAdd<T>>) -> Self::Output {
        Wrapper(other.my_add(self.0))
    }
}

Then you can write things like Wrapper(x) + boxed_myadd_value + another_myadd_value.

Masaki Hara
  • 3,295
  • 21
  • 21
  • How would this work for operator overloading? How would I get `MyAdd` called if the inside of `Bar(x)` was applied like `x + v` where `v` is of type `T`? – Flarp Jan 03 '18 at 01:22
  • @Flarp edited my answer assuming you want `v - x` direction (instead of `x - v`) where `x` is from `Baz(x)`. If you really want `x - v` direction, then it works without hitting coherence restriction. But in this case, the arguments are flipped from the original `T: Sub` trait bound (which means `(v : T) - (x : U)`). – Masaki Hara Jan 03 '18 at 06:11
  • Thanks! Sucks it can't be done without new types, though. – Flarp Jan 03 '18 at 12:00