Notes on adding Constraint Kinds to Rust
a trait can be thought of as a type operator generating a "constraint" - what in Rust would usually be called a bound. For example:
// Declares a new item `Foo` with kind `type -> constraint`
trait Foo { }
// T: Foo applies Foo to `T`, `(type -> constraint)(type) => constraint`
fn bar<T>() where T: Foo { }
Constraints are facts that have to hold true when the generic item is instantiated to a concrete item.
Haskell has an extension which lets you talk about constraints as a first class language feature. This gist is abound a natural way to extend Rust to support some way of talking about constraints, with the context of Rust's syntax (which is very different from Haskell).
Mainly we handle this by dealing with type -> constraint
objects, rather than constraint
objects directly.
trait Foo {
trait Bar;
fn baz<T: Self::Bar>(arg: T);
}
fn quux<T>() where T: Foo + <T as Foo>::Bar
An associated trait is an associated item of the kind type -> constraint
. You can then use it as a bound elsewhere
fn foo<T, trait Trait>() where T: Trait
You could also parameterize items by traits, having the kind type -> constraint
, to use them in bounds
meta trait Monoid {
const EMPTY: Self;
fn append(lhs: Self, rhs: Self) -> Self;
fn concat(elems: Vec<Self>) -> Self {
elems.into_iter().fold(Self::EMPTY, Self::append)
}
}
impl Monoid for Add + Zero {
const EMPTY: Self = Zero::ZERO;
fn append(lhs: Self, rhs: Self) -> Self {
lhs + rhs
}
}
fn noop<T, trait M>(arg: T) -> T where T: M, M: Monoid {
T + <T as M>::EMPTY
}
// mappend::<Add + Zero> adds 2, whereas mappend::<Mul + One> doubles it
fn mappend2<trait M>(arg: i32) -> i32 where i32: M, M: Monoid {
M::append(arg, 2)
}
The use of Self
here is problematic, still need to figure out the syntax around that.
We won't do this any time in the near future, its just interesting to think about.