Skip to content

Instantly share code, notes, and snippets.

@withoutboats
Last active September 6, 2024 20:30
Show Gist options
  • Save withoutboats/8b0eb3012203244bcf3a4e1a4be8819c to your computer and use it in GitHub Desktop.
Save withoutboats/8b0eb3012203244bcf3a4e1a4be8819c 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.

@Manishearth
Copy link

I wonder if we can use Self::Self to refer to the concrete Self type, thus being able to distinguish it from T: Self and Self as a trait

@withoutboats
Copy link
Author

withoutboats commented Mar 6, 2018

I thought about that, worried its pretty opaque. I also considered using Type or SelfType for the type.

I also thought that Self (the trait) would only appear in bounds, so you'd be able to just call both Self and distinguish based on position. However, this isn't true, since the whole point of metatraits is that traits can now appear on the left hand side of the bound constructor, so Self: Foo would refer to either the type or the trait depending on if Foo is a trait or a metatrait.

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