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,
            }
        }
    };

Author: Jeff Culverhouse

I am a remote Sr Software Engineer for ZipRecruiter.com, mainly perl. Learning Rust in my spare time. Plus taking classes at James Madison University. Culverhouse - English: from Old English culfrehūs ‘dovecote’, hence a topographic name for someone living near a dovecote, or possibly a metonymic occupational name for the keeper of a dovecote. ISTP, occasionally INTP.