Understanding scope and shadowing matches - rust

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.

Related

Is there a way to write a function that using odbc::Statement in a loop?

I have working example of a simple loop (mostly taken from the odbc crate's example):
use std::io;
use odbc::*;
use odbc_safe::AutocommitOn;
fn main(){
let env = create_environment_v3().map_err(|e| e.unwrap()).unwrap();
let conn = env.connect_with_connection_string(CONN_STRING).unwrap();
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
let mut sql_text = String::new();
println!("Please enter SQL statement string: ");
io::stdin().read_line(&mut sql_text).unwrap();
stmt = match stmt.exec_direct(&sql_text).unwrap() {
Data(mut stmt) => {
let cols = stmt.num_result_cols().unwrap();
while let Some(mut cursor) = stmt.fetch().unwrap() {
for i in 1..(cols + 1) {
match cursor.get_data::<&str>(i as u16).unwrap() {
Some(val) => print!(" {}", val),
None => print!(" NULL"),
}
}
println!();
}
stmt.close_cursor().unwrap()
}
NoData(stmt) => {println!("Query executed, no data returned"); stmt}
}
}
}
I don't want to create new Statements for each query, as I just can .close_cursor().
I'd like to extract the loop's body to a function, like this:
fn exec_stmt(stmt: Statement<Allocated, NoResult, AutocommitOn>) {
//loop's body here
}
But I just can't! The .exec_direct() method mutably consumes my Statement and returns another. I tried different ways to pass Statement arg to the function (borrow, RefCell, etc), but they all fail when using in a loop. I am still new to Rust, so most likely I just don't know something, or does the .exec_direct's Statement consumption makes it impossible?
There's no nice way to move and then move back values through parameters. It's probably best to copy what .exec_direct does and just make the return type of your function a statement as well.
The usage would then look like this:
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
stmt = exec_stmt(stmnt);
}
and your function signature would be:
fn exec_stmt(stmt: Statement<...>) -> Statement<...> {
match stmt.exec_direct() {
...
}
}
I probably wouldn't recommend this, but if you really wanted to get it to work you could use Option and the .take() method.
fn exec_stmt(some_stmt: &mut Option<Statement<...>>) {
let stmt = some_stmt.take().unwrap();
// do stuff ...
some_stmt.replace(stmt);
}
The odbc-safe crate tried to have each state transition of ODBC reflected in a different type. The odbc-api crate also tries to protect you from errors, but is a bit more subtle about it. Your use case would be covered by the the Preallocated struct.
The analog example from the odbc-api documentation looks like this:
use odbc_api::{Connection, Error};
use std::io::{self, stdin, Read};
fn interactive(conn: &Connection) -> io::Result<()>{
let mut statement = conn.preallocate().unwrap();
let mut query = String::new();
stdin().read_line(&mut query)?;
while !query.is_empty() {
match statement.execute(&query, ()) {
Err(e) => println!("{}", e),
Ok(None) => println!("No results set generated."),
Ok(Some(cursor)) => {
// ...print cursor contents...
},
}
stdin().read_line(&mut query)?;
}
Ok(())
}
This will allow you to declare a function without any trouble:
use odbc_api::Preallocated;
fn exec_statement(stmt: &mut Preallocated) {
// loops body here
}

How does Rust handle shadowed variables?

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.

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

stdin read_line does not wait for user input when called from a git hook

I'm an absolute Rust beginner trying to build a simple confirmation function (yes or no), but I can't get the user to type anything, the function just keeps looping without waiting for user input:
""
""
""
etc.
is the result of the simplified version below.
use std::process;
use std::io;
pub fn confirm() {
loop {
let mut answer = String::new();
io::stdin().read_line(&mut answer)
.ok()
.expect("Failed to read line");
println!("{:?}", answer);
}
}
I've built my function around the guessing game example, and the rest of my program does nothing much, just reading a file and printing text.
Perhaps is due to the way my program (a git hook) is launched?
Assuming that the problem is that your git commit hook is running in an non-interactive environment, you can follow the advice laid out in that question and directly open /dev/tty. Unlike STDIN, we don't treat it as a magical global variable and instead we pass it into the places we need:
use std::io::{self, BufRead, BufReader};
use std::fs::File;
type Tty = BufReader<File>;
fn open_tty() -> io::Result<Tty> {
let f = try!(File::open("/dev/tty"));
Ok(BufReader::new(f))
}
fn confirm(tty: &mut Tty) -> io::Result<String> {
let mut answer = String::new();
try!(tty.read_line(&mut answer));
Ok(answer)
}
fn inner_main() -> io::Result<()> {
let mut tty = try!(open_tty());
let answer = try!(confirm(&mut tty));
println!("The answer was: {}", answer);
Ok(())
}
fn main() {
inner_main().unwrap()
}
Note that this will not be platform independent. Specifically, this is very unlikely to work on Windows!
I've also gone ahead and allowed the io::Result to propagate throughout the program, only panicking at the outermost shell.
Are you testing the function on the Rust Playground? Running this program in a terminal seems to work fine. That being said, there is no guarantee that stdin will block, but you could change the function to check if the string is empty or not, and only return once it is isn't.
use std::io;
fn main() {
println!("{:?}", confirm());
}
fn confirm() -> String {
loop {
let mut answer = String::new();
io::stdin().read_line(&mut answer)
.ok()
.expect("Failed to read line");
if !answer.is_empty() && answer != "\n" && answer != "\r\n" {
return answer
}
}
}

Resources