Using Match – Bob Revisited

We return to our Bob experiment and increase his version number twice!

TRY AGAIN in Scrabble letter - as I try again with an Exercism task, this time using match.
Wait, “TG”? That’s not a word! Challenge!

Remember Bob from a previous post – our tiniest of AI-responders? I got feedback from the Exercism tutor that I had avoided one of Rust‘s most versatile of operators and I should think about using match. So, not to be a jerk, but just to see if I could replace ALL of the ifs in my method, I quickly came up with this solution.

Switch to Using Match

pub fn reply(message: &str) -> &str {
    let message = message.trim();
    let question = message.ends_with('?');
    let any_alpha = message.chars().any(char::is_alphabetic);
    let is_yelling = message.to_ascii_uppercase() == message;
    let is_empty = message.len() == 0;

    match message.is_empty() {
        true => "Fine. Be that way!",
        _ => match any_alpha && is_yelling {
            true => match question {
                true => "Calm down, I know what I'm doing!",
                _ => "Whoa, chill out!",
            },
            _ => match !is_empty && question {
                true => "Sure.",
                _ => "Whatever.",
            },
        },
    }
}

Come On Bob, Get With the Rust

So, it may be my newness to Rust, but I’m not sure version 2 is “more maintainable” code than what I started with. I had thought this Exercism task was just about learning some string methods. Turns out, it is also about learning the match operator. Of course, you wouldn’t code any parser or AI this way anyway – this IS just for practice.

Another tiny thing the tutor pointed out, I did have let check_message = message.trim() in my earlier code. A more idiomatic way in Rust is to reassign it to the same variable, hence the let message = message.trim() in this version. My guess is: less memory allocation, fewer variables for you to keep track of, and you aren’t able to incorrectly use the wrong variable later in the method. Actually, that probably isn’t a Rust idea – that’s just a good programming tip.




Bob With a Trait

Patterns are, I’m told, very powerful in Rust and that’s a big reason to start using match wherever you can. I’ve also learned a bit about traits and impls which I talked about in an earlier post. And that got me thinking maybe I’d work through the syntax to get it working with a trait instead. That idea led to this code.

pub fn reply(message: &str) -> &str {
    enum Quality {
        Empty,
        YellQuestion,
        YellStatement,
        AskQuestion,
        Statement,
    };

    trait HasQuality {
        fn has_quality(&self) -> Quality;
    }

    impl HasQuality for str {
        fn has_quality(&self) -> Quality {
            let message = self.trim();
            let question = message.ends_with('?');
            let any_alpha = message.chars().any(char::is_alphabetic);
            let is_yelling = message.to_ascii_uppercase() == message;
            let is_empty = message.len() == 0;

            match message.is_empty() {
                true => Quality::Empty,
                _ => match any_alpha && is_yelling {
                    true => match question {
                        true => Quality::YellQuestion,
                        _ => Quality::YellStatement,
                    },
                    _ => match !is_empty && question {
                        true => Quality::AskQuestion,
                        _ => Quality::Statement,
                    },
                },
            }
        }
    };

    match message.has_quality() {
        Quality::Empty => "Fine. Be that way!",
        Quality::YellQuestion => "Calm down, I know what I'm doing!",
        Quality::YellStatement => "Whoa, chill out!",
        Quality::AskQuestion => "Sure.",
        _ => "Whatever.",
    }
}

So here, we come up with the idea of a Quality trait and then we implement that trait for the built-in str primative. We’ve expanded what you can do to a str with our own trait! Version 3 of Bob really helps reveal some of the Rust thought patterns I need to hone.

Of course, I made sure Version 3 still passes all of the Exercism tests for this task. This change was approaching my limit of Rust knowledge to get it working without help from a book – just obeying every compiler complaint. However, I cranked this out much faster than previous Rust code, so I think some of this learning (and blogging) is sinking in! I surprised myself enough, that I posted this solution to Exercism as well. I want to hear what the tutor has to say about this method (no pun intended!). Now, I just need to remember to keep using match, traits, impls, and other Rust-supplied power!

Update: 2019-07-17 13:30

Woah, I just got some great advice from the Exercism tutor! You can match on a (expression, expression, ...) so check this out!

    impl HasQuality for str {
        fn has_quality(&self) -> Quality {
            let message = self.trim();
            let question = message.ends_with('?');
            let any_alpha = message.chars().any(char::is_alphabetic);
            let is_yelling = message.to_ascii_uppercase() == message;
            let is_empty = message.len() == 0;

            match (is_empty, any_alpha && is_yelling, question) {
                (true, _, _) => Quality::Empty,
                (false, true, true) => Quality::YellQuestion,
                (false, true, false) => Quality::YellStatement,
                (false, false, true) => Quality::AskQuestion,
                _ => Quality::Statement,
            }
        }
    };

Rust Questions from Beginners, Including Me

Have Rust beginner questions? Discord has Rust channels for the help you’re looking for!

Beginners are encouraged to ask questions
We are stocked full of answers. But… some are “no”.

Information overload and I’m still trying to find a bigger project I can work on that interests me. I have the Interpreter to work on still, and that will take some serious work! But I’m also thinking of going back to small systems of my programming past and playing with writing them in Rust. In the meantime, I thought I’d sneak around the Rust #beginners channel on Discord and give everyone a peak at some questions (and hopefully some answers) that beginners are asking about their early Rust code.


I have a [u8; 10] and I have tried to do a .map() on it but I get:

note: the method 'map' exists but the following trait bounds were not satisfied:
   '&mut [u8; 10] : std::iter::Iterator'
   '&mut [u8] : std::iter::Iterator'

Try .iter().map(...) (Or iter_mut based on what you need).

So, I’m assuming this is an example of that (though, with u16 so my sum doesn’t get too big):

fn main() {
    let list: Vec<u16> = [1,1,2,3,5,8,13,21,34,55].to_vec();
    list.iter().for_each(|n| println!("The next is: {}", n));
    let sum: u16 = list.iter().map(|n| { n }).sum();
    println!("\nSum of list: {}", sum);
}

Note a few things here. We couldn’t do:
list.iter().map(|n| println!("The next is: {}", n));
and the compiler nicely reminds us why:
note: iterators are lazy and do nothing unless consumed

So, iter().for_each() closure works nicely … or we could have used an old-school for loop over the iter() instead.


What would be the Rust equivalent for this in C:
#define TEST 1

A constant is appropriate for this case, for example:
const TEST: i32 = 1;




Student looking in book for answers to his questions
I. Don’t. See. My. Question. Anywhere!

I went ahead and asked my beginner question from the previous blog post: when implementing a trait for several “related” structs, I can require other traits that I depend on… but I can’t make a field (that all the structs share) a requirement… which prevents me from being able to write a default implementation of a trait – I have to write it individually for each struct… is that “just how it is”?

And I got back a good answer:

struct fields are not part of a trait's interface; traits can be implemented by things other than structs e.g. enums, primitive types, closures, …

you can add a health(&self) -> u8 method on your trait that each struct is required to implement, and then use that in the trait's implementation of cur_health

if you want the trait to advertise that its implementors have a concept of "health" then that's one way to go about it

All of that makes sense… and leads me to slightly change the code, to see how it looks with that idea…

struct Player {
    player_name: String,
    real_name: String,
    level: u8,
    health: u8,
}

struct Monster {
    name: String,
    subtype: String,
    hit_dice: u8,
    health: u8,
}

trait Creature {
    fn health(&self) -> u8; 
    fn cur_health(&self) -> String {
        format!("Health: {}", self.health())
    }
}

impl Creature for Player {
    fn health(&self) -> u8 { self.health }
}
impl Creature for Monster {
    fn health(&self) -> u8 { self.health }
}

fn main() {
    let player_1 = Player {
        player_name: "Aragorn".to_string(),
        real_name: "Viggo Mortensen".to_string(),
        level: 20,
        health: 120,
    };
    let monster_1 = Monster {
        name: "Orc".to_string(),
        subtype: "Captain".to_string(),
        hit_dice: 7,
        health: 21,
    };

    println!(" Player 1: {}", player_1.cur_health());
    println!("Monster 1: {}", monster_1.cur_health());
}

Now, health() is a required trait, and not default implemented (since I can’t), but cur_health() to print the string now CAN be defaulted.

So I can implement the simple health() for each struct and let the “fancy” cur_health() default for each struct type. I do think this is an improvement. I’m not sure I’d even implement any of this in this way, but this example is showing me what is possible.

Sit back, relax, stay awhile…

Where I explore some more sources of Rust knowledge and dive into a more complicated project than a simple exercise

Colorized Linux directory listing on screen
I’m watching you, code

I saw two intriguing sources this past weekend. I’ve added both to my resources page. The first one takes a while, but it’s not boring because he works FAST and you have to pay attention in order to follow along! This is @Jonhoo letting us sit over his shoulder while he develops (more advanced solutions) in Rust. But watch this on your widescreen TV or monitor – not with your phone – you need to be able to read the code while he works. Fascinating to watch a library crate be developed start to finish. I never could have gotten the syntax right at this point, but I feel good that I understood probably 90% of what he does! But, what about some (mut simpler, less impressive) Rust coding of my own? See below where I learn some fancy Rust stdin processing.




A Second Source

The second source was a blog post: Writing An Interpreter In Rust by @_chr4 based on his first chapter work adapting Writing An Interpreter In Go by Thorsten Ball to Rust. I started heavily with the code _chr4 had worked out – first on his own and then with some suggestions of readers of that blog post which prompted a second post. But, I then adapted mine to work with “MonkeyCode” either in a source file or keyed interactively. So, I have my project on GitHub as well, but I’m concentrating on just my main.rs file right now:

pub mod lexer;
pub mod token;

use std::env;
use std::fs;
use std::io;

use lexer::Lexer;
use token::Token;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 2 {
        process_file(&args[1]);
    } else {
        process_stdin();
    }
    println!("End.");
}

fn process_stdin() {
    loop {
        print!(">> ");

        let mut line = String::new();
        match io::stdin().read_line(&mut line) {
            Ok(size) if size > 1 => tokenize(&line),
            Ok(size) if size == 1 => println!("CTRL-D to end."),
            Ok(size) if size == 0 => break,
            _ => panic!("Error reading from stdin"),
        }
    }
}

fn process_file(filename: &str) {
    let contents = fs::read_to_string(filename).expect("Reached end of file");
    for line in contents.lines() {
        print!(">> ");
        tokenize(line);
    }
}

fn tokenize(mut line: &str) {
    let mut lexer = Lexer::new(&mut line);

    loop {
        let tok = lexer.next_token();
        println!("{:?}", tok);
        if tok == Token::EndOfLine {
            break;
        }
    }
}

Fancy Rust stdin Processing

Man with arms held up, while sun sets
Success!!

This really seems to work well both ways: either provide a filename as the first parameter and it will read in that file and tokenize the “MonkeyCode” inside, or if you don’t provide an argument, you’ll enter interactive mode and can type “MonkeyCode” live and watch it tokenize each line you enter. I also have it watching stdin for a CTRL-D as a way to exit (of course, CTRL-C works pretty well too, but CTRL-D is a natural, controlled ending of my app).