Coherence in Chalk

 

By Sunjay Varma

twitter.com/sunjay03

github.com/sunjay

Coherence in Chalk

  • Chalk
  • Coherence
  • Logic Programming
  • Lowering Rust Code
  • The Orphan Check
  • The Overlap Check
  • Summary

@sunjay03

How It Is Today

Your Code

Lots of cool stuff I don't know about

Lots of cool stuff I don't know about

Machine Code

Machine Code

Current Traits Implementation

@sunjay03

How It Is Today

Your Code

Lots of cool stuff I don't know about

Lots of cool stuff I don't know about

Machine Code

Machine Code

New Traits Implementation

Your Code

Chalk

Machine Code

Machine Code

Current Traits Implementation

Logic Programming!

@sunjay03

Coherence

@sunjay03

Coherence

trait FavoriteColor {
    fn fav() -> &'static str;
}

struct Sunjay;

mod summer {
    use super::*;
    
    impl FavoriteColor for Sunjay {
        fn fav() -> &'static str { "yellow" }
    }
}

mod winter {
    use super::*;
    
    impl FavoriteColor for Sunjay {
        fn fav() -> &'static str { "blue" }
    }
}

Conflicting Implementations

@sunjay03

💖 Thanks Coherence! 💖

// Uses Hash impl A
fn insert<T: Hash>(...)

// Uses Hash impl B
fn get<T: Hash>(...)

The Hash Table Problem

// In the nom crate
impl Iterator for &[u8] { ... }

// In the serde crate
impl Iterator for &[u8] { ... }

Multiple Dependency Versions

Ecosystem Split

pub struct Foo;

// Only I can add this impl
// because Foo is MY type
impl Display for Foo { ... }

Backwards Compatibility

// In crate A:
trait Foo { ... }
impl<T> Foo for T { ... }

// In crate B:
struct Bar { ... }
// This impl is more specialized
impl Foo for Bar { ... }

// If crate C uses both A and B,
// a single, correct impl should
// always be used consistently

Specialization

@sunjay03

Coherence

  • For any given trait, there are either zero or one impls of that trait that apply for a given set of types

     

 

  • For every impl that could exist, it only exists in one place
MyTrait::foo() // <-- Maps to ONE implementation
impl<T> MyTrait for T { ... } // <-- Can only be written in ONE place

@sunjay03

Bay Area Rust Meetup March 2017

by boats (@withoutboats)

@sunjay03

Coherence in rustc

The Orphan Check

+

The Overlap Check

@sunjay03

  • Every impl abides by the orphan rules
  • Every impl can only exist in one place
  • No two impls overlap
  • Only up to one impl of a trait for a given set of types

The Orphan Rules

@sunjay03

The Orphan Check in rustc

The orphan_check function and the orphan_check_trait_ref function in

rust/src/librustc/traits/coherence.rs

@sunjay03

The Orphan Rules

Given an impl of the form impl<T0…Tn> Trait<P1…Pn> for P0, the impl is allowed if:

  • Trait is local to the current crate
  • Trait is upstream to the current crate and:
  • There is at least one type parameter Pi which, taking fundamental types into account, is local to the current crate (fundamental = &T, &mut T, Box<T> for any T)
  • Within the type Pi,  all type parameters are covered by Pi
  • All types Pj such that j < i do not contain T0…Tn at any level of depth (i.e. the types are fully visible “visible” meaning that the type is a known type and not a type parameter or variable)

@sunjay03

impl<T, U, V, ...> MyTrait<P1...Pn> for P0 { ... }

Impl Type Parameters

Trait

Trait Type Parameters

Implemented Type

@sunjay03

impl<T, U, V, ...> MyTrait<P1...Pn> for P0 { ... }

Is this locally defined in the current crate or is this an Upstream Trait?

At least one local type

Must not contain any of the type parameters T, U, V, ...

Local Trait: Good To Go 🎉

Upstream Trait: Check P0, P1, P2, ..., Pn

Impl Type Parameters

If any of these conditions aren't met, the impl is considered an orphan impl

The Overlap Check

  • Goes through all pairs of impls and checks for any overlap
  • Needs to check all possible compatible universes in order to enforce coherence
  • compatible universes only have semver compatible changes
trait Foo { }
impl<T: Copy> Foo for T { }
impl<T: Eq> Foo for T { }

@sunjay03

Logic Programming

@sunjay03

Logic Programming

  • Take facts, and rules and try to come to some conclusions

👨🏽  💖  🎂

__  💖  🍬     if     __  💖  🎂

fact

rule

conclusion

👨🏽  💖  🍬

Provable because of our fact and rule

"Sunjay loves cake"

"___ loves candy if ___ loves cake"

"Sunjay loves candy"

@sunjay03

Prolog

loves(sunjay, cake)
loves(T, candy) :- loves(T, cake)

loves predicate = 💖

if

?- loves(sunjay, candy)
Yes

Answer

Query

@sunjay03

Chalk

// Each type T implements MyTrait
forall<T> { Implemented(T: MyTrait) }

forall<T> describes all types T

Means "T implements MyTrait"

// There is at least one type T
// that implements MyTrait
exists<T> { Implemented(T: MyTrait) }

exists<T> says that there is some T

forall<T> { ... }

exists<T> { ... }

@sunjay03

Lowering Structs, Traits, and Impls to Logic

impl Foo for Bar { ... }
Implemented(Bar: Foo)

@sunjay03

Lowering to Logic

  • Take each declaration in your program and use what you know about it to produce facts and rules
// In crate "people":
extern crate favorites;
// In crate "favorites":
pub struct Taco { }
pub trait FavoriteColor {
    fn fav() -> &'static str;
}
impl<T: Copy> FavoriteColor for T {
    fn fav() -> &'static str { "purple" }
}
// In crate "std":
pub trait Copy { }
use favorites::*;

struct Sunjay;
impl FavoriteColor for Sunjay {
    fn fav() -> &'static str { "red" }
}

struct Manish;
impl FavoriteColor for Manish {
    fn fav() -> &'static str { "orange" }
}
IsLocal(Sunjay)
IsLocal(Manish)

Implemented(Sunjay: FavoriteColor)
Implemented(Manish: FavoriteColor)

IsUpstream(Taco)

forall<T> { Implemented(T: FavoriteColor)
                :- Implemented(T: Copy) }

DependsOn(people, favorites)

DefinedIn(Sunjay, people)
DefinedIn(Manish, people)

"if"

upstream

current crate

The rustc Guide

@sunjay03

The Orphan Check in Chalk

@sunjay03

The Orphan Rules: Example

// In crate "foo"
pub trait MyTrait<T, U> { }
pub struct Foo { }
// In crate "bar"
extern crate foo;
use foo::*;

struct Bar { }
impl<T> MyTrait<Bar, T> for Foo { }

// Type parameters in order: Foo, Bar, T

impl type parameter

upstream trait

first local type

only fully visible types before the first local type

Good to Go!

The Orphan Rules don't care about any of the types after the first local type

fully visible = no type parameters (e.g. Foo<Bar> but not Foo<T>)

@sunjay03

LocalImplAllowed(T: Trait)

  • LocalImplAllowed(Type: Trait) = "Based on the orphan rules, Type is allowed to implement Trait in the current crate"
struct Turtle;

// Is LocalImplAllowed(Turtle: Display) provable?
// Yes. Turtle is a local type
impl Display for Turtle { ... }

// Is LocalImplAllowed(Vec<Turtle>: Display) provable?
// No. Vec<T> is defined in std and the orphan rules
// say that only std can add impls for it
impl Display for Vec<Turtle> { ... }

@sunjay03

The Orphan Check in Chalk

  • If the trait is local to the current crate:

     
  • If the trait is upstream to the current crate:
forall<Self, T, U> { LocalImplAllowed(Self: MyTrait<T, U>) }
forall<Self, T, U> {
    LocalImplAllowed(Self: MyTrait<T, U>) :-
        IsLocal(Self)
}
forall<Self, T, U> {
    LocalImplAllowed(Self: MyTrait<T, U>) :-
        IsFullyVisible(Self),
        IsLocal(T)
}
forall<Self, T, U> {
    LocalImplAllowed(Self: MyTrait<T, U>) :-
        IsFullyVisible(Self),
        IsFullyVisible(T),
        IsLocal(U)
}
trait MyTrait<T, U> { }

Simulates "finding" the first local type

IsFullyVisible and IsLocal

// In crate "foo":
pub struct Foo { }

// In crate "bar":
extern crate foo;

struct Bar { }
pub struct Spam<T, U> { ... }
IsFullyVisible(Foo)

IsFullyVisible(Bar)

// Notice that there isn't
// any IsFullyVisible(T)
forall<T, U> {
    IsFullyVisible(Spam<T, U>) :-
        IsFullyVisible(T),
        IsFullyVisible(U)
}
// In crate "foo": (current)
pub struct Foo<T> { ... }

// In crate "bar":
extern crate foo;

struct Bar<T> { ... }
forall<T> { IsLocal(Foo<T>) }

@sunjay03

Fully Visible

Not sure if fully visible

Local

Upstream

Foo is local regardless of its type parameter

Chalk

@sunjay03

The Overlap Check in Chalk

@sunjay03

The Overlap Check in Chalk

  • Active area of research
  • Tricky because we need to model all possible compatible impls in the entire universe now and forever in the future
  • Will be figured out soon!

@sunjay03

Summary

  • Coherence is good and very important!
  • Logic programming
  • Modeling the orphan check with logic

Thank you Niko, boats, and the many others who have helped figure all of this out!

@sunjay03

Thank you!

Made with Slides.com