Example of Rust code
bearophile
bearophileHUGS at lycos.com
Fri Aug 10 05:32:27 PDT 2012
(Repost from D.learn.)
Through Reddit I've found a page that shows a small example of
Rust code:
http://www.reddit.com/r/programming/comments/xyfqg/playing_with_rust/
https://gist.github.com/3299083
The code:
https://gist.github.com/3307450
-----------------------------
So I've tried to translate this first part of the Rust code to D
(I have not run it, but it looks correct):
enum expr {
val(int),
plus(&expr, &expr),
minus(&expr, &expr)
}
fn eval(e: &expr) -> int {
alt *e {
val(i) => i,
plus(a, b) => eval(a) + eval(b),
minus(a, b) => eval(a) - eval(b)
}
}
fn main() {
let x = eval(
&minus(&val(5),
&plus(&val(3), &val(1))));
io::println(#fmt("val: %i", x));
}
A comment from the little article:
>putting a & in front of a expression allocates it on the stack
>and gives you a reference to it. so the lifetime of this tree is
>to the end of the run [main] function.<
-----------------------------
The first D version is easy enough to write, but:
- It uses classes, I think each class instance uses more memory
than what's used in the original Rust code.
- All the class instances here are allocated on the Heap. This is
less efficient than the Rust code, where all the data is
stack-allocated.
- This code contains boilerplate, it's long.
- Writing eval() is easy, but in the first version of eval()
there were two bugs.
- The assert(0) in eval() is not nice. There is no compile-time
safety.
- The several dynamic casts in eval() are slow.
interface Expr {}
class Val : Expr {
const int v;
this(in int v_) pure nothrow {
this.v = v_;
}
}
class Plus : Expr {
const Expr x, y;
this(in Expr x_, in Expr y_) pure nothrow {
this.x = x_;
this.y = y_;
}
}
class Minus : Expr {
const Expr x, y;
this(in Expr x_, in Expr y_) pure nothrow {
this.x = x_;
this.y = y_;
}
}
int eval(in Expr e) pure nothrow {
if (Val ve = cast(Val)e)
return ve.v;
else if (Plus pe = cast(Plus)e)
return eval(pe.x) + eval(pe.y);
else if (Minus me = cast(Minus)e)
return eval(me.x) - eval(me.y);
else
assert(0);
}
void main() {
auto ex = new Minus(new Val(5),
new Plus(new Val(3),
new Val(1)));
import std.stdio;
writeln("Val: ", eval(ex));
}
-----------------------------
This second D version uses the same class definitions, but
allocates the class instances on the stack. The code is bug prone
and ugly. The other disadvantages are unchanged:
void main() {
import std.stdio;
import std.conv: emplace;
import core.stdc.stdlib: alloca;
enum size_t size_Val = __traits(classInstanceSize, Val);
enum size_t size_Plus = __traits(classInstanceSize, Plus);
enum size_t size_Minus = __traits(classInstanceSize, Minus);
Val e1 = emplace!Val(alloca(size_Val)[0 .. size_Val], 5);
Val e2 = emplace!Val(alloca(size_Val)[0 .. size_Val], 3);
Val e3 = emplace!Val(alloca(size_Val)[0 .. size_Val], 1);
Plus e4 = emplace!Plus(alloca(size_Plus)[0 .. size_Plus], e2,
e3);
Minus ex2 = emplace!Minus(alloca(size_Minus)[0 ..
size_Minus], e1, e4);
writeln("Val: ", eval(ex2));
}
-----------------------------
A third D version, using tagged structs:
- It doesn't look nice, and it's long.
- Class references can be null, so I have added tests at runtime
in the pre-conditions. In the Rust code the "references" can't be
null.
- The structs are stack-allocated but the main() code is not nice.
- The tags can't const or immutable, otherwise the compiler
doesn't read the actual value of the various tags, assuming it's
always Tag.none.
- Too many casts make this code bug-prone.
import std.stdio;
enum Tag { none, val, plus, minus }
struct Expr {
Tag tag = Tag.none;
}
struct Val {
Tag tag = Tag.val;
immutable int v;
this(int v_) pure nothrow {
this.v = v_;
}
}
struct Plus {
Tag tag = Tag.plus;
const Expr* x, y;
this(in Expr* x_, in Expr* y_) pure nothrow
in {
assert(x_ != null);
assert(y_ != null);
} body {
this.x = x_;
this.y = y_;
}
}
struct Minus {
Tag tag = Tag.minus;
const Expr* x, y;
this(in Expr* x_, in Expr* y_) pure nothrow
in {
assert(x_ != null);
assert(y_ != null);
} body {
this.x = x_;
this.y = y_;
}
}
int eval(in Expr* e) pure nothrow
in {
assert(e);
} body {
final switch (e.tag) {
case Tag.none:
assert(0);
case Tag.val:
return (cast(Val*)e).v;
case Tag.plus:
auto pe = cast(Plus*)e;
return eval(pe.x) + eval(pe.y);
case Tag.minus:
auto me = cast(Minus*)e;
return eval(me.x) - eval(me.y);
}
}
void main() {
const e1 = Val(5);
const e2 = Val(3);
const e3 = Val(1);
const e4 = Plus(cast(Expr*)&e2, cast(Expr*)&e3);
const ex = Minus(cast(Expr*)&e1, cast(Expr*)&e4);
writeln("Val: ", eval(cast(Expr*)&ex));
}
Probably there are ways to improve my D versions, or to write
better versions.
Bye,
bearophile
More information about the Digitalmars-d
mailing list