Rusting

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

    SEP
    2014

    Experimenting With Ownership

    Let's use a trivial exercise to see what we can learn about ownership, moving, borrowing, and more in Rust. Here's the idea:

    1. We'll allocate a list of numbers
    2. We'll add one to each number in the list
    3. We'll print the resulting list of numbers

    This is a simple process requiring only a few lines of code:

    fn main() {
        let mut numbers = vec![1u, 2, 3];
        for n in numbers.mut_iter() {
            *n += 1;
        }
        println!("{}", numbers);
    }
    

    The output is hopefully what we all expect to see:

    $ ./one_function 
    [2, 3, 4]
    

    In this code there is just one variable: numbers. That variable owns a list of numbers on the heap and it's scope is limited to the main() function, which is just a way to say that the data exists for the length of that function call. Since all three steps happen in that one function call, ownership doesn't really affect us here.

    To better examine what ownership really means, let's add one small twist to our exercise:

    • The increment of each number in the list must happen in a separate function

    Read more…

  • 6

    SEP
    2014

    Taking Rust to Task

    Now that I've reached the point where I can get some Rust code running without asking questions in IRC every five minutes, I really wanted to play with some tasks. Tasks are the way Rust handles multiprocessing code. Under the hood they can map one-to-one with operating system threads or you can use a many-to-one mapping that I'm not ready to go into yet.

    Probably one of the most exciting aspect of tasks in Rust, in my opinion, is that unsafe use of shared memory is rejected outright as a compile error. That lead me to want to figure out how you communicate correctly. (Spoiler: the same was you do in Ruby: just pass messages.)

    Ready to dive in, I grossly simplified a recent challenge from work and coded it up in Rust. You can get the idea with a glance at main():

    use std::collections::HashMap;
    
    // ...
    
    fn string_vec(strs: &[&'static str]) -> Vec<String> {
        let mut v = Vec::new();
        for s in strs.iter() {
            v.push(s.to_string());
        }
        v
    }
    
    fn main() {
        let mut services = HashMap::new();
        services.insert("S1".to_string(), string_vec(["A", "B"]));
        services.insert("S2".to_string(), string_vec(["A", "C"]));
        services.insert("S3".to_string(), string_vec(["C", "D", "E", "F"]));
        services.insert("S4".to_string(), string_vec(["D", "B"]));
        services.insert("S5".to_string(), string_vec(["A", "Z"]));
    
        let work = Work(Search::new("A".to_string(), "B".to_string()));
    
        let mut task_manager = TaskManager::new(services);
        task_manager.run(work);
    }
    

    Read more…

    In: Rusting | Tags: Concurrency & Rust | 1 Comment
  • 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());
    }
    

    Read more…

    In: Rusting | Tags: Rust & Style | 0 Comments
  • 21

    AUG
    2014

    Guard Clauses, Rust Style

    When I'm programming in Ruby, I will often use guard clauses to prevent undesirable scenarios. For example, let's say I'm building a simple Stack:

    class Stack
      def initialize
        @numbers = [ ]
      end
    
      def push(number)
        @numbers.push(number)
      end
    
      def peek
        fail "Stack underflow" if @numbers.empty?
    
        @numbers.last
      end
    end
    
    stack = Stack.new
    
    ARGV.each do |number|
      stack.push(number.to_f)
    end
    
    p stack.peek
    

    If I only want to work with numbers everywhere, I add a line like the call to fail() above. This prevents a nil from being returned from peek(), ruining my expectation that I will have numbers everywhere.

    When I first started playing with Rust, I wanted to write code the same way:

    use std::os;
    
    struct Stack {
        numbers: Vec<f64>
    }
    impl Stack {
        fn new() -> Stack {
            Stack{numbers: vec![]}
        }
    
        fn push(&mut self, number: f64) {
            self.numbers.push(number);
        }
    
        fn peek(&self) -> f64 {
            if self.numbers.is_empty() { fail!("Stack underflow"); }
    
            self.numbers.last()
        }
    }
    
    fn main() {
        let mut stack = Stack::new();
    
        for number in os::args().tail().iter() {
            stack.push(from_str(number.as_slice()).expect("Not a number"));
        }
    
        println!("{}", stack.peek());
    }
    

    Read more…

  • 21

    AUG
    2014

    Asking Better Questions

    I've been playing with some Rust lately and learning a bunch of new concepts.

    As part of my experimentation, I built an RPN calculator in the language, just as an exercise that would require a moderate amount of code to be worked out. Honestly, I don't fully understand everything that I had to do to get this code working yet. Maybe 20% of it came about as me following instructions from what seem to be very helpful compiler errors.

    I wanted to start attacking these concepts that I didn't understand to increase my knowledge. Of course, I was impatient. I had some working code and I knew what I was missing, so I jumped into IRC, pointed at the code, and asked some not-at-all complete questions. All I got was crickets.

    This isn't a failing of the Rust community. It's a lesson I have to relearn every now and then. You have to take the time to present a good enough question that answering it is easy enough and worth it. It's hard work to get your head around 100 lines of code and, even if you do, you'll still be missing plenty of context if the question isn't really well formed. Given that, most people just ignore the question. That's probably for the better too, because any answers provided likely would have missed the points I really needed help with.

    Read more…

    In: Rusting | Tags: Community & Rust | 0 Comments