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 if
s 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
primitive. 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,
}
}
};
The post Using Match – Bob Revisited appeared first on Learning Rust.
Top comments (0)