How does Rust handle shadowed variables? - rust

I have strong C/C++ background and am learning Rust these days. Got puzzled by how Rust handles shadowing variables. Particularly, I was expecting that the following code segment shall run without problem because guess is shadowed from a String to an integer before the next time it is called as a String in read_line.
Reading the API document, I understand read_line would append the next input to guess. But after the shadowing, should guess be considered as an integer and such appending be invalid? Please help.
fn main() {
let secret_number = 10;
let mut guess = String::new();
loop {
//guess.clear(); // uncomment this line will make it work.
println!("Please input your guess:");
io::stdin()
.read_line(&mut guess)
.expect("Failed to read guess.");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
};
}
}

Shadowing is a purely syntactic phenomenon. It has no effect in the generated code, that is the generated code would be identical if you chose a different name for each variable. It is just that the shadowed variable cannot be referenced by name.
In particular, in your code:
let mut guess = String::new(); //1
...
loop {
io::stdin().read_line(&mut guess)... //2
let guess: u32 = match guess.trim()... ; //3
match guess.cmp(...) // 4
}
//5
The usages in line 2 and 3 refer to the declared variable in line 1, while the usage in line 4 refers to the declaration in line 3. There is no change in the type of the variable, nor there is any change in lifetimes. Simply they are two different variables that happen to have the same name, so that it will be impossible for your program to access the variable in line 1 from line 4.
In fact, after the loop finises, in line 5, the name guess will again refer to the variable in line 1, because the other one went out of scope.

Related

Save command line argument to variable and use a default if it is missing or invalid

The idea here is simple but I have tried three different ways with different errors each time: read in a string as an argument, but if the string is invalid or the string isn't provided, use a default.
Can this be done using Result to detect a valid string or a panic?
The basic structure I expect:
use std::env;
use std::io;
fn main() {
let args: Vec<String> = args().collect();
let word: Result<String, Error> = &args[1].expect("Valid string");
let word: String = match word {
Ok(word) = word,
Err(_) = "World",
}
println!("Hello, {}", word);
}
So, there are a lot of issues in your code.
First and foremost, in a match statement, you do not use =, you use =>.
Additionally, your match statement returns something, which makes it not an executing block, but rather a returning block (those are not the official terms). That means that your blocks result is bound to a variable. Any such returning block must end with a semicolon.
So your match statement would become:
let word: String = match word {
Ok(word) => word,
Err(_) => ...,
};
Next, when you do use std::env, you do not import all of the functions from it into your namespace. All you do is that you create an alias, so that the compiler turns env::<something> intostd::env::<something> automatically.
Therefore, this needs to be changed:
let args: Vec<String> = env::args().collect();
The same problem exists in your next line. What is Error? Well, what you actually mean is io::Error, that is also not imported due to the same reasons stated above. You might be wondering now, how Result does not need to be imported. Well, it is because the Rust Team has decided on a certain set of functions and struct, which are automatically imported into every project. Error is not one of them.
let word: Result<String, io::Error> = ...;
The next part is wrong twice (or even thrice).
First of all, the operation [x] does not return a Result, it returns the value and panics if it is out-of-bounds.
Now, even if it was a result, this line would still be wrong. Why? Because you expect(...) the result. That would turn any Result into its value.
Now, what you are looking for is the .get(index) operation. It tries to get a value and if it fails, it returns None, so it returns an option. What is an option? It is like a result, but there is no error value. It must be noted that get() returns the option filled with a reference to the string.
The line should look something like this:
let word: Option<&String> = args.get(1);
Now you have two options to handle default values, but before we come to that, I need to tell you why your error value is wrong.
In Rust, there are two kinds of Strings.
There is ยด&str`, which you can create like this:
let a: &str = "Hello, World!";
These are immutable and non-borrowed strings stored on the stack. So you cannot just create a new one with arbitary values on the fly.
On the other hand, we have mutable and heap-allocated Strings.
let mut a: String = String::new();
a.push_str("Hello, World!");
// Or...
let b: String = String::from("Hello, World");
You store your arguments as a String, but in your match statement, you try to return a &str.
So, there are two ways to handle your error:
let word: Option<&String> = args.get(1);
let word: String = match word {
Some(word) => word.to_string(),
None => String::from("World"),
};
If you do not want to allocate that second string, you can also use
let word: Option<&String> = args.get(1);
let word: &str = match word {
Some(word) => word.as_str(),
None => "World",
};
The second option, unwrap_or
let args: Vec<String> = env::args().collect();
let default = &String::from("World");
let word: &String = args.get(1).unwrap_or(default);
println!("Hello, {}", word);
is a bit uglier, as it requires you to bind the default value to a variable. This will do what your match statement above does, but it's a bit prettier.
This works too:
let word: &str = args.get(1).unwrap_or(default);
So this is my favourite version of your program above:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let default = &String::from("World");
let word: &str = args.get(1).unwrap_or(default);
println!("Hello, {}", word);
}
But this one works too:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let word: Option<&String> = args.get(0);
let word: &str = match word {
Some(word) => word.as_str(),
None => "World",
};
println!("Hello, {}", word);
}

Extension on the rust textbook's guessing game is showing odd behavior

So I'm extending the guessing game to basically ask the user if they want to play again. If they type y, the program will return to the main game loop and if they type n, it just breaks out of the current loop and the program ends. If they type anything else, in theory it should just jump to the top of the play_again loop and reassign the yae_or_nay variable as whatever the player inputs next. But it doesn't do that, or at least it looks like it overwrites it incorrectly. Am I reassigning the variable incorrectly? Heres the code (note that the first half of the program is almost the same as in the textbook, but since the program is so short I just decided to include the whole thing):
extern crate rand;
use std::io;
use std::cmp::Ordering;
use rand::Rng; // random number generation library from rand
fn main() {
println!("Guessing game!\n");
let mut answer = rand::thread_rng().gen_range(1,101);
'gameloop: loop {
println!("Please print your guess:");
let mut yae_or_nay = String::new();
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess : u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Not a number!");
continue;
}
};
match guess.cmp(&answer) {
Ordering::Less => println!("Higher!"),
Ordering::Greater => println!("Lower!"),
Ordering::Equal => {
println!("Correct! Would you like to play again? (y/n)");
'play_again: loop {
io::stdin().read_line(&mut yae_or_nay)
.expect("Failed to read line.");
match yae_or_nay.trim() { // match against a string
"y" => {
answer = rand::thread_rng().gen_range(1,101);
println!("Playing again...");
continue 'gameloop;
},
"n" => {
println!("Thanks for playing! Exiting now.");
break
},
_ => {
println!("what? You entered {}", &yae_or_nay);
continue 'play_again
}
};
}
}
}
}
}
And here's a snippet of the console output:
46
Correct! Would you like to play again? (y/n)
i
what? You entered i
y
what? You entered i
y
n
what? You entered i
y
n
. // period here for formatting sake, not actually in console
As you can see, the way my program is reassigning yae_or_nay seems pretty strange. Anyone know what's going on? Thanks in advance for any help.
The issue is that you never clear yae_or_nay, and io::stdin().read_line(&mut yae_or_nay) will append to the string, not replace its content.
Read all bytes until a newline (the 0xA byte) is reached, and append them to the provided buffer.
Either you should do
yae_or_nay.clear();
before you read into it, or likely better would be to move the declaration to
'play_again: loop {
let mut yae_or_nay = String::new();
io::stdin().read_line(&mut yae_or_nay)
.expect("Failed to read line.");

Guessing game, error on shadowing guess bind

I'm following the Rust tutorial but I'm stuck on this code (the last snippet in the page):
extern crate rand;
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is {}", secret_number);
loop {
println!("Please input your guess");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
}
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
When I run cargo run I have the following error:
src/main.rs:23:47: 23:48 error: expected one of `.`, `;`, or an operator, found `{`
src/main.rs:23 let guess: u32 = guess.trim().parse() {
^
What's the right syntax?
There is a syntax error and the compiler message is directing your attention to the wrong place on the line to fix the problem.
The parse method evaluates to a value. This expression should not be followed by a block, causing the syntax error reported by the compiler.
https://doc.rust-lang.org/std/string/struct.String.html#method.parse
The example you linked to has the keyword match between the assignment and call to parse. The match keyword takes an expression and branches based on the value of the expression. The block contains the branching patterns and expressions. In this case it is also destructuring the Result into either an u32 or u32::Err.
https://doc.rust-lang.org/book/match.html
Below is an example that separates the parse and match for clarity.
// Store result of parsing in a variable
let parse_result = guess.trim().parse();
// Destructure the result
let guess: u32 = match parse_result {
// If parse succeeded evaluate to the number
Ok(num) => num,
// If parse failed repeat the loop
Err(_) => continue,
};
You forgot to add the match keyword before calling guess.trim().parse()
That line should look like this:
let guess : u32 = match guess.trim().parse() {...
source: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html

Understanding scope and shadowing matches

I'm trying to improve on the final guessing game sample code a bit. Particularly, I plan to output "Please input a number!" if the user does not input a number rather than "Please input your guess." again. I'm doing this with an inner loop. The code below does work:
let guess: u32;
loop {
let mut guess_str = String::new();
io::stdin().read_line(&mut guess_str)
.ok()
.expect("Failed to read line");
guess = match guess_str.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please input a number!");
continue;
}
};
break;
}
I'd like to avoid the guess_str if I can by properly shadowing the matches. If I change guess_str to guess, Rust complains of use of possibly uninitialized variable: `guess`. I'm not sure how the variable could possibly be uninitialized if it's impossible for it to not be uninitialized with the code above. Is there any way to do this only using guess?
Let's look at a simpler reproduction:
fn make_guess() -> u32 {
let guess;
{
let mut guess;
guess = 1;
}
guess
}
Here, you create an outer variable guess and then shadow it inside the block. When you assign the value 1 to guess, you are assigning to the inner variable. The outer variable is never set to anything, thus you end up with the "use of possibly uninitialized variable" error.
Is there any way to only use one variable
Indirectly, yes. I'd extract the code to a function. When you have a successful guess, you can simply return. Otherwise you allow the loop to occur:
fn make_guess() -> u32 {
loop {
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
match guess.trim().parse() {
Ok(num) => return num,
Err(_) => {
println!("Please input a number!");
}
}
}
}
This avoids the shadowing completely, avoids having to use an explicit continue, and adds a small amount of abstraction and organization to your code.

Creating a vector of strings using the new std::fs::File

Porting my code from old_io to the new std::io
let path = Path::new("src/wordslist/english.txt");
let display = path.display();
let mut file = match File::open(&path) {
// The `desc` field of `IoError` is a string that describes the error
Err(why) => panic!("couldn't open {}: {}", display,
Error::description(&why)),
Ok(file) => file,
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(why) => panic!("couldn't read {}: {}", display,
Error::description(&why)),
Ok(s) => s,
};
let words: Vec<_> = s.words().collect();
So this works but requires me to have a mutable string s to read the file contents, and then use words().collect() to gather into into a vector,
Is there a way to read the contents of a file to a vector using something like words() WITHOUT reading it to the mutable buffer string first? My thought is that this would be more performant in situations where the collect() call might happen at a later point, or after a words().map(something).
Your approach has a problem. .words() operates on an &str (string slice) which needs a parent String to refer to. Your example works fine because the Vec produced by s.words().collect() resides in the same scope as s, so it won't outlive the source string. But if you want to move it somewhere else, you'll need to end up with a Vec<String> instead of a Vec<&str>, which I'm assuming you already want if you're concerned about intermediate buffers.
You do have some options. Here's two that I can think of.
You can iterate over the characters of the file and split on whitespace:
// `.peekable()` gives us `.is_empty()` for an `Iterator`
// `.chars()` yields a `Result<char, CharsError>` which needs to be dealt with
let mut chars = file.chars().map(Result::unwrap).peekable();
let mut words: Vec<String> = Vec::new();
while !chars.is_empty() {
// This needs a type hint because it can't rely on info
// from the following `if` block
let word: String = chars.take_while(|ch| !ch.is_whitespace()).collect();
// We'll have an empty string if there's more than one
// whitespace character between words
// (more than one because the first is eaten
// by the last iteration of `.take_while()`)
if !word.is_empty() {
words.push(word);
}
}
You can wrap the File object in a std::io::BufReader and read it line-by-line with the .lines() iterator:
let mut reader = BufReader::new(file);
let mut words = Vec::new();
// `.lines()` yields `Result<String, io::Error>` so we have to handle that.
// (it will not yield an EOF error, this is for abnormal errors during reading)
for line in reader.lines().map(Result::unwrap) {
words.extend(line.words().map(String::from_str));
}
// Or alternately (this may not work due to lifetime errors in `flat_map()`
let words: Vec<_> = reader.lines().map(Result::unwrap)
.flat_map(|line| line.words().map(String::from_str))
.collect();
It's up to you to decide which of the two solutions you prefer. The former is probably more efficient but maybe less intuitive. The latter is easier to read, especially the for-loop version, but allocates intermediate buffers.

Resources