I started programming Rust a couple of days ago by working through the official documentation. Now I'm trying to challenge my understanding of Rust by working through the book "Exercises for Programmers" by Brian P. Hogan (The Pragmatic Programmers).
The first exercise is to write a program that asks the user for a name and prints out a greeting using that name. Input, string concatenation and output should be done in three distinct steps.
What is your name? Patrick
Hello, Patrick, nice to meet you.
The name will be entered at the same line as the initial prompt. Here's my solution:
use std::io;
use std::io::Write;
fn main() {
print!("What is your name? ");
match io::stdout().flush() {
Ok(_) => print!(""),
Err(error) => println!("{}", error),
}
let mut name = String::new();
match io::stdin().read_line(&mut name) {
Ok(_) => {
name = name.trim().to_string();
if name.len() > 0 {
let greeting = "Hello, ".to_string() + &name + &", nice to meet you!".to_string();
println!("{}", greeting);
} else {
println!("No name entered, goodbye.");
}
}
Err(error) => println!("{}", error),
}
}
The print! macro doesn't actually output the prompt until I call flush. flush needs error handling, so I need both to handle the Ok and the Err case. In case of Ok, there's nothing useful to do, so I just print! an empty string.
Is there a shorter way to handle this? Maybe the error handling can be skipped or simplified somehow, or the whole print!/flush approach is the wrong one. (Everything works fine, but I could write this shorter in C, after all...)
As other people have said, make sure to read the error handling chapter.
In most cases, you don't want to use println! to report errors. Either you should return the error from your function and let the caller deal with it, or you should use panic! to abort that thread and potentially the process.
match io::stdout().flush() {
Ok(_) => print!(""),
Err(error) => println!("{}", error),
}
Instead of printing nothing (which is inefficient), you can just... do nothing:
match io::stdout().flush() {
Ok(_) => (),
Err(error) => println!("{}", error),
}
Since you don't care about the success case, you can use an if let:
if let Err(error) = io::stdout().flush() {
println!("{}", error);
}
Replacing the println with a panic! would be even better:
if let Err(error) = io::stdout().flush() {
panic!("{}", error);
}
This is almost exactly what Option::unwrap does (source), except it also returns the successful value when present:
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
However, it's even better to use Option::expect which allows you to specify an additional error message:
io::stdout().flush().expect("Unable to flush stdout");
Applying that twice:
use std::io::{self, Write};
fn main() {
print!("What is your name? ");
io::stdout().flush().expect("Unable to flush stdout");
let mut name = String::new();
io::stdin()
.read_line(&mut name)
.expect("Unable to read the line");
let name = name.trim();
if !name.is_empty() {
println!("Hello, {}, nice to meet you!", name);
} else {
println!("No name entered, goodbye.");
}
}
Note that there's no need to re-allocate a String, you can just shadow name, and there's no need to use format just to print out stuff.
Since Rust 1.26.0, you could also choose to return a Result from main:
use std::io::{self, Write};
fn main() -> Result<(), io::Error> {
print!("What is your name? ");
io::stdout().flush()?;
let mut name = String::new();
io::stdin().read_line(&mut name)?;
let name = name.trim();
if !name.is_empty() {
println!("Hello, {}, nice to meet you!", name);
} else {
println!("No name entered, goodbye.");
}
Ok(())
}
but I could write this shorter in C, after all...
I would encourage / challenge you to attempt this. Note that every memory allocation in this program is checked, as is every failure case dealing with the standard output. Many people are not aware that C's printf returns an error code that you should be checking. Try outputting to a pipe that has been closed for an example.
Related
How do I know what error happened when piping Results via and_then in Rust? For example using nested matches I could do this:
use std::num::ParseIntError;
fn get_num(n: u8) -> Result<u8, ParseIntError> {
// Force a ParseIntError
"foo ".parse()
}
fn add_one(n: u8) -> Result<u8, ParseIntError> {
Ok(n + 1)
}
fn main() {
match get_num(1) {
Ok(n) => {
match add_one(n) {
Ok(n) => println!("adding one gives us: {}", n),
Err(why) => println!("Error adding: {}", why)
}
},
Err(why) => println!("Error getting number: {}", why),
}
}
Or I can use and_then to pipe the result of the first function to the second and avoid a nested match:
match get_num(1).and_then(add_one) {
Ok(n) => println!("Adding one gives us: {}", n),
Err(why) => println!("Some error: {}.", why)
}
How do I conditionally and idiomatically determine the error in the second form, using and_then? Do I have to type check the error? Sure I can display the error like I did above, but let's say I want to preface it with something related to the kind of error it is?
The normal way would be to use map_err to convert the initial errors to something richer you control e.g.
enum MyError {
Parsing,
Adding
}
get_num(1).map_err(|_| MyError::Parsing)
.and_then(|v| add_one(v).map_err(|_| MyError::Adding)
sadly as you can see this requires using a lambda for the and_then callback: and_then must return the same error type as the input, so here it has to yield a Result<_, MyError>, wjhich is not the return type of add_one.
And while in other situations we could recover somewhat with ?, here the same original error type is split into two different values.
My original goal is to fetch a list of words, one on each line, and to put them in a HashSet, while discarding comment lines and raising I/O errors properly. Given the file "stopwords.txt":
a
# this is actually a comment
of
the
this
I managed to make the code compile like this:
fn stopword_set() -> io::Result<HashSet<String>> {
let words = Result::from_iter(
BufReader::new(File::open("stopwords.txt")?)
.lines()
.filter(|r| match r {
&Ok(ref l) => !l.starts_with('#'),
_ => true
}));
Ok(HashSet::from_iter(words))
}
fn main() {
let set = stopword_set().unwrap();
println!("{:?}", set);
assert_eq!(set.len(), 4);
}
Here's a playground that also creates the file above.
I would expect to have a set of 4 strings at the end of the program. To my surprise, the function actually returns a set containing a single string with all words concatenated:
{"aofthethis"}
thread 'main' panicked at 'assertion failed: `(left == right)` (left: `1`, right: `4`)'
Led by a piece of advice in the docs for FromIterator, I got rid of all calls to from_iter and used collect instead (Playground), which has indeed solved the problem.
fn stopword_set() -> io::Result<HashSet<String>> {
BufReader::new(File::open("stopwords.txt")?)
.lines()
.filter(|r| match r {
&Ok(ref l) => !l.starts_with('#'),
_ => true
}).collect()
}
Why are the previous calls to from_iter leading to unexpected inferences, while collect() works just as intended?
A simpler reproduction:
use std::collections::HashSet;
use std::iter::FromIterator;
fn stopword_set() -> Result<HashSet<String>, u8> {
let input: Vec<Result<_, u8>> = vec![Ok("foo".to_string()), Ok("bar".to_string())];
let words = Result::from_iter(input.into_iter());
Ok(HashSet::from_iter(words))
}
fn main() {
let set = stopword_set().unwrap();
println!("{:?}", set);
assert_eq!(set.len(), 2);
}
The problem is that here, we are collecting from the iterator twice. The type of words is Result<_, u8>. However, Result also implements Iterator itself, so when we call from_iter on that at the end, the compiler sees that the Ok type must be String due to the method signature. Working backwards, you can construct a String from an iterator of Strings, so that's what the compiler picks.
Removing the second from_iter would solve it:
fn stopword_set() -> Result<HashSet<String>, u8> {
let input: Vec<Result<_, u8>> = vec![Ok("foo".to_string()), Ok("bar".to_string())];
Result::from_iter(input.into_iter())
}
Or for your original:
fn stopword_set() -> io::Result<HashSet<String>> {
Result::from_iter(
BufReader::new(File::open("stopwords.txt")?)
.lines()
.filter(|r| match r {
&Ok(ref l) => !l.starts_with('#'),
_ => true
}))
}
Of course, I'd normally recommend using collect instead, as I prefer the chaining:
fn stopword_set() -> io::Result<HashSet<String>> {
BufReader::new(File::open("stopwords.txt")?)
.lines()
.filter(|r| match r {
&Ok(ref l) => !l.starts_with('#'),
_ => true,
})
.collect()
}
I am trying to iterate over characters in stdin. The Read.chars() method achieves this goal, but is unstable. The obvious alternative is to use Read.lines() with a flat_map to convert it to a character iterator.
This seems like it should work, but doesn't, resulting in borrowed value does not live long enough errors.
use std::io::BufRead;
fn main() {
let stdin = std::io::stdin();
let mut lines = stdin.lock().lines();
let mut chars = lines.flat_map(|x| x.unwrap().chars());
}
This is mentioned in Read file character-by-character in Rust, but it does't really explain why.
What I am particularly confused about is how this differs from the example in the documentation for flat_map, which uses flat_map to apply .chars() to a vector of strings. I don't really see how that should be any different. The main difference I see is that my code needs to call unwrap() as well, but changing the last line to the following does not work either:
let mut chars = lines.map(|x| x.unwrap());
let mut chars = chars.flat_map(|x| x.chars());
It fails on the second line, so the issue doesn't appear to be the unwrap.
Why does this last line not work, when the very similar line in the documentation doesn't? Is there any way to get this to work?
Start by figuring out what the type of the closure's variable is:
let mut chars = lines.flat_map(|x| {
let () = x;
x.unwrap().chars()
});
This shows it's a Result<String, io::Error>. After unwrapping it, it will be a String.
Next, look at str::chars:
fn chars(&self) -> Chars
And the definition of Chars:
pub struct Chars<'a> {
// some fields omitted
}
From that, we can tell that calling chars on a string returns an iterator that has a reference to the string.
Whenever we have a reference, we know that the reference cannot outlive the thing that it is borrowed from. In this case, x.unwrap() is the owner. The next thing to check is where that ownership ends. In this case, the closure owns the String, so at the end of the closure, the value is dropped and any references are invalidated.
Except the code tried to return a Chars that still referred to the string. Oops. Thanks to Rust, the code didn't segfault!
The difference with the example that works is all in the ownership. In that case, the strings are owned by a vector outside of the loop and they do not get dropped before the iterator is consumed. Thus there are no lifetime issues.
What this code really wants is an into_chars method on String. That iterator could take ownership of the value and return characters.
Not the maximum efficiency, but a good start:
struct IntoChars {
s: String,
offset: usize,
}
impl IntoChars {
fn new(s: String) -> Self {
IntoChars { s: s, offset: 0 }
}
}
impl Iterator for IntoChars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let remaining = &self.s[self.offset..];
match remaining.chars().next() {
Some(c) => {
self.offset += c.len_utf8();
Some(c)
}
None => None,
}
}
}
use std::io::BufRead;
fn main() {
let stdin = std::io::stdin();
let lines = stdin.lock().lines();
let chars = lines.flat_map(|x| IntoChars::new(x.unwrap()));
for c in chars {
println!("{}", c);
}
}
See also:
How can I store a Chars iterator in the same struct as the String it is iterating on?
Is there an owned version of String::chars?
My program should read some parameters from the command line. If someone does not provide an optional password parameter the program should ask for it. Therefore the corresponding field is modeled as an Option type.
This works well if the parameter is provided from the command line ( option having the value of Some("..."), but the branch matching the None case does not ask for input.
The simplified program looks like
fn main() {
use std::io::{self,Read};
let arg : Option<String> = None; // Does not terminate
//let arg : Option<String> = Some("Some arg".to_string()); // works well printing 'Some arg'
println!("Checking for password");
let password = match arg {
Some(val) => val.to_string(),
None => {
print!("Password:");
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).unwrap();
buffer
}
};
println!("password is {}", password);
}
Running the program preinitialized with Some("Some arg") prints the string "password is Some arg" to the console as expected, but switching to the None does nothing, even not terminating the program.
Could you spot my mistake or give me some advice? I am using rustc verison rustc 1.4.0 (8ab8581f6 2015-10-27). Thanks in advance.
You need to use read_line():
fn main() {
use std::io::{self,Read};
let arg : Option<String> = None; // Does not terminate
//let arg : Option<String> = Some("Some arg".to_string()); // works well printing 'Some arg'
println!("Checking for password");
let password = match arg {
Some(val) => val.to_string(),
None => {
print!("Password:");
let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap();
buffer
}
};
println!("password is {}", password);
}
The read_to_string() function reads until end of file.
Your program does indeed read input, but you need to send it the EOF character (Ctrl-D on Linux) for it to continue executing.
I've been trying to find an easy way to read variables in Rust, but haven't had any luck so far. All the examples in the Rust Book deal with strings AFAIK, I couldn't find anything concerning integers or floats that would work.
I don't have a Rust compiler on this machine, but based in part on this answer that comes close, you want something like...
let user_val = match input_string.parse::<i32>() {
Ok(x) => x,
Err(_) => -1,
};
Or, as pointed out in the comments,
let user_val = input_string.parse::<i32>().unwrap_or(-1);
...though your choice in integer size and default value might obviously be different, and you don't always need that type qualifier (::<i32>) for parse() where the type can be inferred from the assignment.
To read user input, you always read a set of bytes. Sometimes, you can interpret those bytes as a UTF-8 string. You can then further interpret the string as an integral or floating point number (or lots of other things, like an IP address).
Here's a complete example of reading a single line of input and parsing it as a 32-bit signed integer:
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Not a valid string");
let input_num: i32 = input.trim().parse().expect("Not a valid number");
println!("Your number plus one is {}", input_num + 1);
}
Note that no user-friendly error handling is taking place. The program simply panics if reading input or parsing fails. Running the program produces:
$ ./input
41
Your number plus one is 42
A set of bytes comprises an input. In Rust, you accept the input as a UTF-8 String. Then you parse the string to an integer or floating point number. In simple ways you accept the string and parse it, then write an expect`` statement for both, to display a message to the user what went wrong when the program panics during runtime.
fn main() {
let mut x = String::new();
std::io::stdin().read_line(&mut x)
.expect("Failed to read input.");
let x: u32 = x.trim().parse()
.expect("Enter a number not a string.");
println!("{:?}", x);
}
If the program fails to parse the input string then it panics and displays an error message. Notice that the program still panics and we are not handling an error perfectly. One more thing to notice is that we can use the same variable name x and not some x_int because of the variable shadowing feature. To handle the error better we can use the match construct.
fn main() {
let mut x = String::new();
match std::io::stdin().read_line(&mut x) {
Ok(_) => println!("String has been taken in."),
Err(_) => {
println!("Failed to read input.");
return;
},
};
let x: u32 = match x.trim().parse() {
Ok(n) => {
println!("Converted string to int.");
n
},
Err(_) => {
println!("Failed to parse.");
return;
},
};
println!("{:?}", x);
}
This is longer way but a nicer way to handle errors and input and parse a number.