接受使用者輸入

接受使用者的輸入的一個簡單的方式是用 std::io::stdin。這意味著"標準輸入" (standard input),也就是來自鍵盤的輸入。用 stdin() 可以獲得使用者的輸入內容,但是接下來你就會想用 .read_line() 把它放到 &mut String 中。這裡是那種情境的簡單範例,但它既能用、也不能用:

use std::io;

fn main() {
    println!("Please type something, or x to escape:");
    let mut input_string = String::new();

    while input_string != "x" { // 這是不能用的部分
        input_string.clear(); // 首先清除 String 內容. 不然會一直加入東西進去
        io::stdin().read_line(&mut input_string).unwrap(); // 從使用者獲得的 stdin, 並把它放進去 read_string
        println!("You wrote {}", input_string);
    }
    println!("See you later!");
}

這裡是輸出看起來的樣子:

Please type something, or x to escape:
something
You wrote something

Something else
You wrote Something else

x
You wrote x

x
You wrote x

x
You wrote x

它接受我們的輸入,然後把它還給我們,它甚至知道我們輸入了 x。但它並沒有退出程式。唯一的辦法是關閉視窗,或者輸入 ctrl 和 c。讓我們把 println! 中的 {} 改為 {:?},來得到更多資訊(如果你喜歡用巨集,也可以使用 dbg!(&input_string))。現在它說:

Please type something, or x to escape:
something
You wrote "something\r\n"
Something else
You wrote "Something else\r\n"
x
You wrote "x\r\n"
x
You wrote "x\r\n"

這是因為鍵盤輸入其實不只是 something,而是 somethingEnter 鍵。有個簡單的方法可以修正這個問題,叫做 .trim(),它可以把所有的空白字元都去掉。順便說一下,這些字元都是空白字元:

U+0009 (horizontal tab, '\t')
U+000A (line feed, '\n')
U+000B (vertical tab)
U+000C (form feed)
U+000D (carriage return, '\r')
U+0020 (space, ' ')
U+0085 (next line)
U+200E (left-to-right mark)
U+200F (right-to-left mark)
U+2028 (line separator)
U+2029 (paragraph separator)

這樣就可以把 x\r\n 變成只剩 x 了。現在它可以用了:

use std::io;

fn main() {
    println!("Please type something, or x to escape:");
    let mut input_string = String::new();

    while input_string.trim() != "x" {
        input_string.clear();
        io::stdin().read_line(&mut input_string).unwrap();
        println!("You wrote {}", input_string);
    }
    println!("See you later!");
}

現在會印出:

Please type something, or x to escape:
something
You wrote something

Something
You wrote Something

x
You wrote x

See you later!

還有另一種使用者輸入叫 std::env::Args(env 的意思是環境 environment )。Args 是使用者啟動程式時打字輸入的內容。其實在程式執行時總是至少有一個 Arg。讓我們寫個程式,裡面只使用 std::env::args() 印出它們,來看看它們是什麼。

fn main() {
    println!("{:?}", std::env::args());
}

如果我們寫 cargo run,就會像這樣印出來:

Args { inner: ["target\\debug\\rust_book.exe"] }

讓我們給它更多輸入來看看它的作用。我們輸入 cargo run but with some extra words 來執行,會給我們:

Args { inner: ["target\\debug\\rust_book.exe", "but", "with", "some", "extra", "words"] }

真有趣。而當我們瀏覽 Args 文件時,我們看到它實作了 IntoIterator。這意味著我們可以做全部疊代器我們所知的一切事情來讀取和改變它。讓我們試試這個:

use std::env::args;

fn main() {
    let input = args();

    for entry in input {
        println!("You entered: {}", entry);
    }
}

現在它說:

You entered: target\debug\rust_book.exe
You entered: but
You entered: with
You entered: some
You entered: extra
You entered: words

你可以看到第一個引數總是程式名,所以你經常會想跳過它,比如這樣:

use std::env::args;

fn main() {
    let input = args();

    input.skip(1).for_each(|item| {
        println!("You wrote {}, which in capital letters is {}", item, item.to_uppercase());
    })
}

會印出:

You wrote but, which in capital letters is BUT
You wrote with, which in capital letters is WITH
You wrote some, which in capital letters is SOME
You wrote extra, which in capital letters is EXTRA
You wrote words, which in capital letters is WORDS

Args 的一個常見用途是用於使用者設定。你可以確保使用者寫出你需要的輸入,只有在正確的情況下才執行程式。這裡有個小程式能讓字母變大(大寫)或變小(小寫):

use std::env::args;

enum Letters {
    Capitalize,
    Lowercase,
    Nothing,
}

fn main() {
    let mut changes = Letters::Nothing;
    let input = args().collect::<Vec<_>>();

    if input.len() > 2 {
        match input[1].as_str() {
            "capital" => changes = Letters::Capitalize,
            "lowercase" => changes = Letters::Lowercase,
            _ => {}
        }
    }

    for word in input.iter().skip(2) {
      match changes {
        Letters::Capitalize => println!("{}", word.to_uppercase()),
        Letters::Lowercase => println!("{}", word.to_lowercase()),
        _ => println!("{}", word)
      }
    }
    
}

這裡的一些範例是它給的輸出:

輸入:cargo run please make capitals

make capitals

輸入:cargo run capital

// 這裡沒東西輸出...

輸入:cargo run capital I think I understand now

I
THINK
I
UNDERSTAND
NOW

輸入:cargo run lowercase Does this work too?

does
this
work
too?

除了使用者給予的 Args,在 std::env::args() 中找得到的那些,還有系統變數 Vars。這些都是非使用者輸入的程式基本設定。你可以用 std::env::vars() 把它們全部輸出成格式 (String, String),會有非常多筆資料。舉例來說:

fn main() {
    for item in std::env::vars() {
        println!("{:?}", item);
    }
}

只要這樣做就能秀出你目前使用者會話 (user session) 的所有資訊。它將會顯示像這樣的資訊:

("CARGO", "/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo")
("CARGO_HOME", "/playground/.cargo")
("CARGO_MANIFEST_DIR", "/playground")
("CARGO_PKG_AUTHORS", "The Rust Playground")
("CARGO_PKG_DESCRIPTION", "")
("CARGO_PKG_HOMEPAGE", "")
("CARGO_PKG_NAME", "playground")
("CARGO_PKG_REPOSITORY", "")
("CARGO_PKG_VERSION", "0.0.1")
("CARGO_PKG_VERSION_MAJOR", "0")
("CARGO_PKG_VERSION_MINOR", "0")
("CARGO_PKG_VERSION_PATCH", "1")
("CARGO_PKG_VERSION_PRE", "")
("DEBIAN_FRONTEND", "noninteractive")
("HOME", "/playground")
("HOSTNAME", "f94c15b8134b")
("LD_LIBRARY_PATH", "/playground/target/debug/build/backtrace-sys-3ec4c973f371c302/out:/playground/target/debug/build/libsqlite3-sys-fbddfbb9b241dacb/out:/playground/target/debug/build/ring-cadba5e583648abb/out:/playground/target/debug/deps:/playground/target/debug:/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib:/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib")
("PATH", "/playground/.cargo/bin:/playground/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
("PLAYGROUND_EDITION", "2018")
("PLAYGROUND_TIMEOUT", "10")
("PWD", "/playground")
("RUSTUP_HOME", "/playground/.rustup")
("RUSTUP_TOOLCHAIN", "stable-x86_64-unknown-linux-gnu")
("RUST_RECURSION_COUNT", "1")
("SHLVL", "1")
("SSL_CERT_DIR", "/usr/lib/ssl/certs")
("SSL_CERT_FILE", "/usr/lib/ssl/certs/ca-certificates.crt")
("USER", "playground")
("_", "/usr/bin/timeout")

所以如果你需要這些資訊,Vars 就是你想要的。

要獲得單獨的 Var 最簡單的方法是使用 env! 巨集。你只要給它變數名,它就會給你 &str 的值。如果變數拼寫錯誤或不存在就沒作用了,所以如果你不確定那就用 option_env!。如果我們在 Playground 上寫這樣:

fn main() {
    println!("{}", env!("USER"));
    println!("{}", option_env!("ROOT").unwrap_or("Can't find ROOT"));
    println!("{}", option_env!("CARGO").unwrap_or("Can't find CARGO"));
}

那我們會得到輸出:

playground
Can't find ROOT
/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo

所以 option_env! 永遠會是比較安全的巨集。如果你實際上是想讓程式在找不到環境變數 (environment variable) 時崩潰,那麼 env! 會更好。