Constrained traits

There are currently discussions how to use traits in const contexts. This reminded me of the recently accepted RFC: Overconstraining and omitting unsafe in impls of unsafe trait methods. This concept is called over-constraining because it allows trait impls to have more constraints than the trait. When applied to constness, it means that a trait function could be const in the impl but not in the trait:

struct Foo;

impl Add for Foo {
    type Output = Foo;
    const fn add(self, rhs: Foo) -> Foo {
        Foo
    }
}

const fn bar() {
    let _ = Foo + Foo;
}

Since over-constraining for safety has already been accepted, so it's only natural to apply it to constness as well.

The problem is that this is doesn't work for generic types. So I had the idea to create constrained traits which are identical to another trait but with additional constraints. Here's a possible syntax:

trait Foo {
    type Ty;

    unsafe fn f1();
    fn f2(self) -> Self::Ty;
    fn f3();
}

trait Bar constrains Foo {
    type Ty: ToString;

    fn f1();
    const fn f2(self) -> Self::Ty;
}

const fn usage(bar: impl Bar) -> impl ToString {
    bar.f2()
}

So when Bar is required somewhere, any Foo can be used, as long as

  • Ty implements ToString
  • f1 is safe
  • f2 is const

Bar doesn't need to be explicitly implemented; any type that implements Foo with the additional above constraints also implements Bar.

This has several advantages:

  • Not all trait functions must be const. For example, this makes it possible to use Iterators in const functions where only the next method is const.
  • Non-const functions can be added to traits backwards-compatibly
  • It could be useful in other areas unrelated to const functions. Over-constraining could be applied to safety, constness, types, trait/lifetime bounds and possibly more in the future.

Obviously the biggest drawback is the boilerplate needed to write a generic const function. Still I'd like to hear your opinions on this! Note that this is still in the "brainstorming" phase and not a finished proposal :smiley:

6 Likes

This reminds me of Go's interfaces, where anything that implements the right functions can be converted to that interface. Here, something like that is being applied to a specific trait's members, rather than to loose functions.