10

I'm trying to port this program that computes the nth derivative of x^x symbolically to Rust. It seems to be mostly easy:

use std::rc::Rc;

type Expr = Rc<Expr2>;

enum Expr2 {
    Int(i32),
    Var(String),
    Add(Expr, Expr),
    Mul(Expr, Expr),
    Pow(Expr, Expr),
    Ln(Expr),
}

use Expr2::*;

fn pown(a: i32, b: i32) -> i32 {
    match b {
        0 => 1,
        1 => a,
        n => {
            let b = pown(a, b / 2);
            let b2 = b * b;
            if n % 2 == 0 {
                b2
            } else {
                b2 * a
            }
        }
    }
}

fn add(f: Expr, g: Expr) -> Expr {
    match (f, g) {
        (Int(m), Int(n)) => Int(m + n),
        (Int(0), f) => f,
        (f, Int(n)) => add(Int(n), f),
        (f, Add(Int(n), g)) => add(Int(n), add(f, g)),
        (Add(f, g), h) => add(f, add(g, h)),
        (f, g) => Add(f, g),
    }
}

fn mul(f: Expr, g: Expr) -> Expr {
    match (f, g) {
        (Int(m), Int(n)) => Int(m * n),
        (Int(0), f) => Int(0),
        (Int(1), f) => f,
        (f, Int(n)) => mul(Int(n), f),
        (f, Mul(Int(n), g)) => mul(Int(n), mul(f, g)),
        (Mul(f, g), h) => mul(f, mul(g, h)),
        (f, g) => Mul(f, g),
    }
}

fn pow(f: Expr, g: Expr) -> Expr {
    match (f, g) {
        (Int(m), Int(n)) => Int(pown(m, n)),
        (f, Int(0)) => Int(1),
        (f, Int(1)) => f,
        (Int(0), f) => Int(1),
        (f, g) => Pow(f, g),
    }
}

fn ln(f: Expr) -> Expr {
    match f {
        Int(1) => Int(0),
        f => Ln(f),
    }
}

fn d(x: String, f: Expr) -> Expr {
    match f {
        Int(_) => Int(0),
        Var(y) => if x == y {
            x
        } else {
            y
        },
        Add(f, g) => add(d(x, f), d(x, g)),
        Mul(f, g) => add(mul(f, d(x, g)), mul(g, d(x, f))),
        Pow(f, g) => mul(
            pow(f, g),
            add(mul(mul(g, d(x, f)), pow(f, Int(-1))), mul(ln(f), d(x, g))),
        ),
        Ln(f) => mul(d(x, f), pow(f, Int(-1))),
    }
}

fn count(f: Expr) -> i32 {
    match f {
        Int(_) | Var(_) => 1,
        Add(f, g) | Mul(f, g) | Pow(f, g) => count(f) + count(g),
        Ln(f) => count(f),
    }
}

fn string_of_expr(f: Expr) -> String {
    count(f).to_string();
}

fn nest(n: i32, f: Expr, x: Expr) -> Expr {
    if n == 0 {
        x
    } else {
        nest(n - 1, f, f(x))
    }
}

fn deriv(f: Expr) -> Expr {
    let df = d("x", f);
    format!("D({}) = {}", string_of_expr(f), string_of_expr(df));
    df
}

fn main() {
    let x = "x";
    let f = pow(x, x);
    // FIXME: Read command-line argument
    let df = nest(9, deriv, f);
    format!("{}", count(df));
}

The type needs to be converted into a reference counted enum in Rust and pattern matching makes for very similar code except... it doesn't work. From what I can gather, patterns in Rust cannot match upon the result of dereferencing an Rc. So, no matter what I do, it fails on nested patterns like (f, Add(Int(n), g)).

Am I missing something or is it really impossible for nested patterns to match over recursive datatypes in Rust? Apparently there is something called "box syntax" to dereference inside a pattern (amongst other things) that has been on the drawing board for four years.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
J D
  • 48,105
  • 13
  • 171
  • 274
  • 1
    *is it really impossible for nested patterns to match over recursive datatypes in Rust?* — no, that's completely possible and standard. *patterns in Rust cannot match upon the result of dereferencing an `Rc`* — you have no dereferencing in this code at all. – Shepmaster Jan 14 '18 at 04:10
  • 2
    @Shepmaster: "that's completely possible and standard". Great. What am I doing wrong and how do I do it right? – J D Jan 14 '18 at 12:59
  • 2
    I copied a bit more than I meant to when quoting. I was going for just *is it really impossible for nested patterns to match*, which is possible and normal. Sorry for any false hope. – Shepmaster Jan 14 '18 at 16:01
  • See also [A simple formula interpreter](https://stackoverflow.com/q/30004175/155423) – Shepmaster Jan 14 '18 at 16:34

1 Answers1

6

It appears that yes, it is currently impossible to do this. Recursive datatypes require indirection, e.g. Rc. Indirection requires dereferences when matching against nested patterns. There is no way to dereference inside a pattern match in Rust today.

The workaround is to compile your patterns by hand, i.e. as if you only had C-style switch.

A feature called "box patterns" has been discussed since 2014 that may solve this problem in the future but it hasn't shipped.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
J D
  • 48,105
  • 13
  • 171
  • 274