1

I would like to create two traits, SaveSource and SaveDestination, such that when some types implement these traits, the function:

fn save(a, b)

must be implemented for all a : SaveSource and b : SaveDestination (and when a new type is added to either SaveSource or SaveDestination, it must implement the save function for all existing SaveDestinations or SaveSources.

Is something like this even possible? If not, is there anything I can use instead to get the same result?

  • I must be missing something about your question; `fn(a: A, b: B) { ... }` would appear to already do what you ask. Why would either of the traits themselves implement the function? – Shepmaster Jun 01 '17 at 18:19
  • @Shepmaster: I can't have a single function for all the types. The implementation will be different for all pairs of types. – user8099375 Jun 01 '17 at 18:21
  • For example, if I have three Sources, `S1`, `S2`, and `S3`, and two Destinations, `D1` and `D2`, i'll have 6 functions: `save(a: S1, b: D1)`, `save(a: S1, b: D2)`, `save(a: S2, b: D1)`, ... – user8099375 Jun 01 '17 at 18:23
  • 1
    Are you familiar with the [double dispatch pattern](https://en.wikipedia.org/wiki/Double_dispatch)? – Shepmaster Jun 01 '17 at 18:26
  • 2
    Why does it matter that `a` and `b` implement the `SaveSource` / `SaveDestination` traits if all the implementations have to be different anyway? – Shepmaster Jun 01 '17 at 18:27
  • *for all existing `SaveDestination`s or `SaveSource`s* — how would this be possible, considering that traits are open-ended and any consumer of your code can choose to implement that trait if it chooses? Do you want someone adding an additional implementation of one of those traits to "break" compilation of existing types? – Shepmaster Jun 01 '17 at 18:29
  • 2
    Design the traits so that the common functionality can be implemented using only the methods on the traits, and put the implementation specific details hidden behind the implementations. – zstewart Jun 02 '17 at 03:07

1 Answers1

1

You cannot force the compiler to emit an error when some combination of A and B doesn't implement save. But you can have a generic function that requires that the combination of the specific A and B it receives implements save.

To do that, we need to wrap save in a trait and implement it on some type that includes both A and B; the simplest option is a tuple. (Coherence may get in the way if the trait and the types are not all in the same crate, though.)

trait Save {
    fn save(self);
}

struct Foo; // sample save source
struct Bar; // sample save destination

// save is defined for the combination of `Foo` and `Bar`
impl Save for (Foo, Bar) {
    fn save(self) {
        unimplemented!()
    }
}

// in order to call this, the type `(A, B)` must implement `Save`    
fn call_save<A, B>(a: A, b: B)
where
    (A, B): Save
{
    (a, b).save();
}

fn main() {
    // this call compiles because `impl Save for (Foo, Bar)` is present
    call_save(Foo, Bar);
}

You can also do it for references:

trait Save {
    fn save(self);
}

struct Foo;
struct Bar;

impl<'a, 'b> Save for (&'a Foo, &'b Bar) {
    fn save(self) {
        unimplemented!()
    }
}

fn call_save<'a, 'b, A, B>(a: &'a A, b: &'b B)
where
    (&'a A, &'b B): Save
{
    (a, b).save();
}

fn main() {
    call_save(&Foo, &Bar);
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155