Idea: Trait-definition-time delegation


#1

This is something I’ve wanted for a while now, which I run into occasionally:

trait A {
    type K;
    fn foo(self) -> Self::K;
}

trait B: A { // note that B: A. this is required!
    fn bar(self) -> Self;

    // the gramatical change here
    // is that associated items allow paths,
    // rather than identifiers, for their names
    type A::K = Self;
    fn A::foo(self) -> Self { self.bar() }
}

With this definition we have the following desugaring:

impl B for C {
    fn bar(self) -> Self { self.baz() }
}
// if the following impl is permitted by
// coherence and orphan rules, it is automatically
// generated. Note that it is *not* a blanket <T: B> impl!
impl A for C {
    type K = Self;
    fn foo(self) -> Self { self.bar() }
}

The use-case I care about is the following, for a numerics project I’m working on:

/// an algebra over F_2
trait F2Alg: Add<Self> + AddAssign<Self> + Mul<Self> + MulAssign<Self> {
    fn add(self) -> Self;
    fn mul(self) -> Self;
    fn zero() -> Self;
    fn one() -> Self;

    type Add::Output = Self;
    fn Add::add(self, that: Self) -> Self { F2Alg::add(self, that) }
    // and so on
}

// an impl for one of the algebras I work with
impl F2Alg for Milnor {
    fn add(self) -> Self { .. }
    // ..
}

I deal with this by generating operator impls via macros, but it’s quite cumbersome. I’m not aware of where the delegation RFC ended up going, but this is of a distinctly different flavor of what that was about AFAIK; this is at trait-definition time, rather than at implementation time.

This would also make implementing some of the num traits for custom numerics much less of a hassle (as with any large trait hierarchy).


#2

Would the following approach work for you? (Playground)

trait A {
    type K;
    fn foo(self) -> Self::K;
}

trait B {
    fn bar(self) -> Self;
}

impl <T: B> A for T {
    type K = Self;
    fn foo(self) -> Self { self.bar() }
}

#3

This is possibly already covered by the specialization RFC:

trait A {
    type K;
    fn foo(self) -> Self::K;
}

trait B: A {
    fn bar(self) -> Self;
}

default /* <-- Nota bene! */ impl<T: B> A for T {
    type K = Self;
    fn foo(self) -> Self { self.bar() }
}

#4

The reason I’m explicitly avoiding a blanket impl A is because the intended use-case is when A, B, and C are all defined in different crates… Does specialization allows you to define blanket impls for foreign traits? In that case specialization subsumes this feature.


#5

I think it will be better to relax coherence rules and somehow allow impl<T: B> A for T { .. } if A is a foreign trait and B is a trait defined in the current trait. For example I would very much like to implement io::Read for digest::Input trait if std feature is enabled, but unfortunately compiler is not smart enough to recognize that such impl will not cause any issues.


#6

The default impl thing is not a blanket impl but a “blueprint” sorta…


#7

I’m going to go meditate on the actual RFC and see if I can sort this out…