Skip to content

Instantly share code, notes, and snippets.

@Centril
Forked from withoutboats/constraint-kinds.md
Created March 5, 2018 20:29
Show Gist options
  • Save Centril/baebf170a8a588e0cc269305e126d2b7 to your computer and use it in GitHub Desktop.
Save Centril/baebf170a8a588e0cc269305e126d2b7 to your computer and use it in GitHub Desktop.
Constraint Kinds in Rust

Notes on adding Constraint Kinds to Rust

Background: Constraint Kinds

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.

Associated traits

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

Trait parameters

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 traits

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.

Note

We won't do this any time in the near future, its just interesting to think about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment