Skip to content

Latest commit

 

History

History
99 lines (77 loc) · 2.61 KB

HOW.md

File metadata and controls

99 lines (77 loc) · 2.61 KB

How?

Obviously with trait trickery, what else. At the core of what makes this possible is the following feature of Rust:

struct X;
impl X {
    fn f(self) {}
}

trait Q {
    fn f(self) {}
}
impl Q for X {}

I.e. I can have for one type the same method name twice, once directly implemented and once via a trait. If that is the case, the directly implemented function shadows the trait implemented function. Of course both impl X {...} and impl Q for X {...} can contain type & trait constraints, allowing us to control dependent on types which of the two f actually gets executed for the expression x.f() for some value x: X.

In the implementation this is utilized as follows:

struct Done<T>(T);

impl<T> Done<T> {
    fn arm0(self) -> Self {
        self
    }
    fn arm1(self) -> Self {
        self
    }
    fn done(self) -> T {
        self.0
    }
}

struct Wrapper<T>(T);

trait Catch<T> {
    fn arm0(self) -> Self;
    fn arm1(self) -> Self;
}

impl<T> Catch<T> for Wrapper<T> {
    fn arm0(self) -> Self {
        self
    }
    fn arm1(self) -> Self {
        self
    }
}

trait Match0 {
    type ReturnType = /* arm 0 RHS expression type */;
    fn match(self) -> Self::ReturnType;
}

impl Match0 for /* arm 0 match type */ {
    fn match(self) -> Self::ReturnType {
        /* arm 0 RHS expression */
    }
}

impl<T: Match0> Wrapper<T> {
    fn arm0(self) -> Done<<T as Match0>::ReturnType> {
        Done(Match0::match(self.0))
    }
}

trait Match1 {
    type ReturnType = /* arm 1 RHS expression type */;
    fn match(self) -> Self::ReturnType;
}

impl Match1 for /* arm 1 match type */ {
    fn match(self) -> Self::ReturnType {
        /* arm 1 RHS expression */
    }
}

impl<T: Match1> Wrapper<T> {
    fn arm1(self) -> Done<<T as Match1>::ReturnType> {
        Done(Match1::match(self.1))
    }
}

And so on with arm{N} & Match{N} for as many arms the match_type expression has.

Then the actual expression to be evaluated is:

Wrapper(x).arm0().arm1().done();

If x matches the type of the 0th match arm, the arm0 in the expression will be Wrapper::arm0, which in turn executes the RHS of the match arm. The arm1 in the expression then will be Done::arm1, and finally done() will unwrap the result.

If x does not match the type of the 0th match arm, the arm0 in the expression will be Catch::arm0. If x does match the type of the 1st match arm, then arm1 will be Wrapper::arm1, and done again will unwrap the result.

If x matches neither the 0th match arm, nor the 1st match arm, we get a type error (at compile time!), since done is not implemented for Wrapper.