Last time, we did a basic setup of our Rust programming environment so that we could get started. This week, we’re going to look at some sample code for a simple name calling game. My inspirations for this post are chapter 4 of Build Your Own Lisp and, of course, the Calvin & Hobbes comic strips where Calvin decides to be “The incredibly annoying human echo.”
Here’s the source code for this week:
use std::io; | |
use std::io::Write; | |
fn main() { | |
//println! is a macro that writes a line to stdout and then flushes the | |
//stdout buffer | |
println!("The inredibly annoying Rust echo strikes again!"); | |
//get a reference to stdin | |
let stdin = io::stdin(); | |
//create a mutable string to hold what the user types. | |
let mut buffer = String::new(); | |
//loop is a keyword for intentionally declaring an infinite loop. | |
// `while true` would give a compiler warning. | |
loop { | |
//print! is like println!, but it doesn't append a newline, and it doesn't | |
//flush stdout | |
print!(">>>"); | |
//manually flush stdout so that ">>>" prompt appears | |
io::stdout().flush().expect("Could not flush stdout. Panic!"); | |
//stdout.flush() and stdin.readline() both return a Result, which is | |
//an enumerated type, that is either Ok, or some error. | |
//the expect() call tells the compiler, basically, "on Ok do nothing, | |
//but on Error, fail with this message" | |
stdin.read_line(&mut buffer).expect("Could not read stdin. Panic!"); | |
//echo back the user's input, because we're incredibly annoying | |
println!("Haha! {}", buffer.trim()); | |
//clear the input string; only echo the most recent input. | |
buffer.clear(); | |
} | |
} | |
What the code actually does is pretty straightforward: It prompts the user to enter some text, and then replays the text back to the user. Of course, when we actually build a REPL, we’ll need to take input from the user, then actually do something, then display the result to the user. This example omits the middle part.
There are a few differences between the C code in Build Your Own Lisp and the Rust code above. The overwhelming theme of these changes is safety. Rust is really into safety.
First off, operations that might fail (with what Eric Lippert would call “exogenous exceptions”) often return a Result. Result has the attribute must_use
, which means that the compiler will warn you if you ignore the return value of a function that returns Result. In other words, if you do something that might fail, and then don’t check whether it failed, the compiler will warn you. The expect() call is a shorthand way of saying, “If this operation failed, just exit with an error message.” In our case, if my program can’t print to stdout or read from stdin, there’s no way it can sensibly continue to execute, so it should return with an error. That way, the program can’t get into an undefined state. It either succeeded and read a string from stdin, or it failed and exited with an error.
Rust also has a try!()
and a ?
operator that do similar things, but try! can only be used inside functions that return a Result, and main() returns a unit.
Great! So we have a program that will repeat whatever the user types back to the user, until they break with CTRL+C or just close the console window. Next time, we’ll learn to do something with the user input.
Till next time, happy learning!
-Will