回顾一下猜数字游戏:产生 0-100 随机整数,由用户输入数字,程序会提示输入值相比于随机数的大小,从而使用户猜对。

  1. use rand::Rng;
  2. use std::cmp::Ordering;
  3. use std::io;
  4. fn main() {
  5. println!("Guess the number!");
  6. // 注意 gen_range(1..101) 而不是 gen_range(1, 101)
  7. let secret_number = rand::thread_rng().gen_range(1..101);
  8. println!("The secret number is: {}", secret_number);
  9. loop {
  10. println!("Please input your guess.");
  11. let mut guess = String::new();
  12. io::stdin()
  13. .read_line(&mut guess)
  14. .expect("Failed to read line");
  15. // let guess: u32 = guess.trim().parse().expect("Please type a number!");
  16. let guess: u32 = match guess.trim().parse() {
  17. Ok(x) => x,
  18. Err(_) => {
  19. print!("Not a NUMBER. ");
  20. continue;
  21. }
  22. };
  23. println!("You guessed: {}", guess);
  24. match guess.cmp(&secret_number) {
  25. Ordering::Less => println!("Too small!"),
  26. Ordering::Greater => println!("Too big!"),
  27. Ordering::Equal => {
  28. println!("You win!");
  29. break;
  30. }
  31. }
  32. }
  33. }

🤔 print! 和 stdin 不在同一行显示

应用此例子时我产生一个疑问:
println!("Please input your guess: "); 和 输入(stdin)并不在同一行。这很好理解,因为 println! 显然多了一个换行。
如果需要打印输出和捕获输入在一行显示,是不是换成 print! 就行了呢?结果很可能是这样:

  1. 输入的内容
  2. Please input your guess:

而不是我们期待的:

  1. Please input your guess: 输入的内容

在 python3 中,我们可以直接使用 variable = input("Please input your guess: ") 来获取终端输入,并且输出与输入显示在同一行。
然而在 Rust 中,即便 print! 先于 stdin 出现,终端显示并不是这个顺序。
原因在于终端输出(stdout)在新的行(换行)之后才刷新出现
由于 print! 语句既不包含换行,又不以换行结尾,所以无法触发屏幕刷新和显示。
stdin().read_line() 有一个换行符 \n,刷新了屏幕,所以导致意料之外的情况。
我们可以用 println!({:?}, ) 验证一下捕获输入的换行符:

  1. use std::io;
  2. fn main() {
  3. print!("Input: ");
  4. let mut t = String::new();
  5. io::stdin().read_line(&mut t).expect("Failed to read line.");
  6. println!("{:?}", t);
  7. }

其他 3:💭 gussing game 深究 - 图1

💡 解决方式1:flush 手动刷新

既然 print! 不触发刷新,那么自己手动刷新一下就能解决不在同一行显示的问题,代码是 std::io::stdout().flush(),不过 flush 方法还需调用 std::io::Write trait,最终的代码:

  1. use std::io::{self, Write};
  2. fn main() {
  3. print!("Input: ");
  4. // 如果没有把 flush 的结果赋值,编译器会提示 unused `Result` that must be used
  5. let _ = std::io::stdout().flush();
  6. let mut t = String::new();
  7. io::stdin().read_line(&mut t).expect("Failed to read line");
  8. println!("{:?}", t);
  9. }
  1. Input: 1
  2. "1\n"

💡💡 解决方法2:dialoguer 终端对话利器

达到 py 的 input 类似的效果,甚至更 fancy 的操作:使用 dialoguer crate,参考 dialoguer/examples/input.rs,比如以下例子:

  1. use dialoguer::{theme::ColorfulTheme, Input};
  2. fn main() {
  3. // 类似 py3 的 input
  4. let input: String = Input::new()
  5. .with_prompt("Your name")
  6. .interact_text()
  7. .unwrap();
  8. println!("Hello {}!", input);
  9. // 终端带样式的 input
  10. let input: String = Input::with_theme(&ColorfulTheme::default())
  11. .with_prompt("Your name")
  12. .interact_text()
  13. .unwrap();
  14. println!("Hello {}!", input);
  15. }

其他 3:💭 gussing game 深究 - 图2
这个 crate 提供的终端对话应用例子:
其他 3:💭 gussing game 深究 - 图3


例子来源:

  1. Rust Book: guessing-game-tutorial
  2. Rust 程序设计语言 简体中文版:编写 猜猜看 游戏

源讨论:
stackoverflow: how-do-i-print-stdout-and-get-stdin-on-the-same-line-in-rust