Rusting

The section where I examine what I am learning when compiling Rust code.

27

AUG
2014

Which Types to Type

I've mentioned before that I'm writing some Rust code, specifically an RPN calculator as a simple exercise. I'm going to dump the code here so we can discuss one aspect of it, but do remember that I'm very new to Rust and this code could surely be better:

use std::fmt;
use std::os;

struct Stack {
    numbers: Vec<f64>
}
impl Stack {
    fn new() -> Stack {
        Stack{numbers: vec![]}
    }

    fn is_empty(&self) -> bool {
        self.numbers.is_empty()
    }

    fn push(&mut self, number: f64) {
        self.numbers.push(number);
    }

    fn result(&self) -> f64 {
        *self.numbers.last().expect("Stack empty.")
    }

    fn add(&mut self)      { self._do_binary_operation(|l, r| l + r); }
    fn subtract(&mut self) { self._do_binary_operation(|l, r| l - r); }
    fn multiply(&mut self) { self._do_binary_operation(|l, r| l * r); }
    fn divide(&mut self)   { self._do_binary_operation(|l, r| l / r); }

    fn _do_binary_operation(&mut self, operation: |f64, f64| -> f64) {
        let r = self.numbers.pop().expect("Stack underflow.");
        let l = self.numbers.pop().expect("Stack underflow.");
        self.numbers.push(operation(l, r));
    }
}
impl fmt::Show for Stack {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut s = String::new();
        let mut i = self.numbers.len();
        for number in self.numbers.iter() {
            i -= 1;
            s = s.add(&format!("{}: {}\n", i, number));
        }
        s.pop_char();
        write!(f, "{}", s)
    }
}

struct Tokenizer {
    tokens: Vec<String>,
    i:      uint
}
impl Tokenizer {
    fn new(expression: &str) -> Tokenizer {
        Tokenizer{
          tokens: expression.split(|c: char| c.is_whitespace())
                            .map(|s| s.to_string())
                            .collect(),
          i:      0
        }
    }

    fn has_next_token(&self) -> bool {
        self.i < self.tokens.len()
    }

    fn next_token(&mut self) -> &str {
        if !self.has_next_token() { fail!("Tokens exhausted.") }

        let token = self.tokens[self.i].as_slice();
        self.i   += 1;
        token
    }
}

struct RPNCalculator {
    stack:  Stack,
    tokens: Tokenizer
}
impl RPNCalculator {
    fn new(stack: Stack, tokens: Tokenizer) -> RPNCalculator {
        RPNCalculator{stack: stack, tokens: tokens}
    }

    fn calculate(&mut self) -> f64 {
        while self.tokens.has_next_token() {
            let token = self.tokens.next_token();
            if !self.stack.is_empty() {
                println!("{}", self.stack);
            }
            println!("T: {}\n", token);
            match token {
                "+" => { self.stack.add(); }
                "-" => { self.stack.subtract(); }
                "*" => { self.stack.multiply(); }
                "/" => { self.stack.divide(); }
                n   => { self.stack.push(from_str(n).expect("Not a number.")); }
            }
        }
        if !self.stack.is_empty() {
            println!("{}\n", self.stack);
        }
        self.stack.result()
    }
}

fn main() {
    let     expression = os::args();
    let     stack      = Stack::new();
    let     tokenizer  = Tokenizer::new(expression[1].as_slice());
    let mut calculator = RPNCalculator::new(stack, tokenizer);
    println!("{}", calculator.calculate());
}

Rust has type inference meaning that it can often guess which type a given variable will hold. I haven't used many languages with this feature, so I'm not sure just how good Rust's implementation is compared to others, but it sure seems dynamite to me so far.

Just counting bare let statements, I'm using at least 10 variables that have no explicit type specification. There are many more in closures and passed values. Consider this line:

n => { self.stack.push(from_str(n).expect("Not a number.")); }

The function from_str() returns something like Option<&N>, where N is a primitive numeric type. Then expect() reduces that to N (from Some(N)) and I guess Rust figured out N had to be a float because it's handed off to this method definition:

fn push(&mut self, number: f64) {

I may have exactly how it figured all of that out wrong, but the end result is still awesome to me.

So which types do you really need? I've played around a little and here's what seems to work for me: declare the types of parameters and return values in function definitions. The rest mostly seems to take care of itself.

There are exceptions of course. You don't seem to need types on the self used in a method definition. The documentation is written without those and I've just copied that style. Also, occasionally the compiler will insist your clarify the type of a closure variable or let assignment. I add those only when prodded though and that seems quite rare so far.

In: Rusting | Tags: Rust & Style | 0 Comments
Comments (0)
Leave a Comment (using GitHub Flavored Markdown)

Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

Ajax loader