根據之前接觸其他程式語言的經驗,在已經熟悉其中一種的情況下,其實可以直接看別的語言簡單的函數(function)來看出一點端倪或特色。
不過 Hello world 能看到的有限,所以今天的目標是寫一個再複雜一點點 Rust 的 echo function,預期要從 terminal 獲取輸入,並將該輸入值進行處理後輸出到 terminal 顯示。
首先要取得 terminal 的輸入我們需要先把 io
library 引入當前作用域。
use std::io;
use
類似其他語言的 import
,引用其他的 library 到當前的作用域,在這個檔案裡面就可以直接使用。std
是 Rust 內建的 library, io
是這個 library 底下的其中一個 module,專門用於輸入/輸出操作。可以把 std
視為一個包含 io
還有其他 module 的容器,也可以視為一個 root module。
::
在這邊代表的是命名空間路徑:用來訪問和引用特定 module、型別(types)或其他項目的路徑。
命名空間路徑的概念是像文件系統的層次結構,項目可以被唯一標識,從而避免命名衝突,同時也幫助組織和封裝的概念,讓我們可以更好地理解和使用。
有點好奇所以順便找一下 std
是裝在哪邊,從 VSCode Go to Definition
打開 source code 和檔案位置,可以看到在 ~/.rustup/toolchains
底下。
$ cd ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src
接著看一下有哪些 package 也在這個 path 底下,全部都隸屬於 Rust 標準庫(std)。
$ tree . -d -L 1
.
├── backtrace
├── collections
├── env
├── error
├── f128
├── f16
├── f32
├── f64
├── ffi
├── fs
├── hash
├── io
├── net
├── num
├── os
├── panic
├── path
├── prelude
├── process
├── sync
├── sys
├── sys_common
├── thread
└── time
另外雖然現在還不需要安裝其他外部依賴,但如果安裝也不會像 npm
那樣存在專案的路徑下然後產生 node_module
資料夾,cargo
會把這些依賴放在另外一個特定的全局目錄中,目的是為了避免重複下載和方便管理,細節這邊先不贅述。
再來回到正題,我要把 terminal 輸入的值先存在一個變數,所以要先看一下 io module 要怎麼用,以及在 Rust 要如何宣告變數以及賦值。
Rust 的文件提示都滿完整的,這次直接先找 io
的說明,可以從官網或是我是從 IDE 直接查詢:
Standard input and output
A very common source of input is standard input:
use std::io;
fn main() -> io::Result<()> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
println!("You typed: {}", input.trim());
Ok(())
}
結果範例就把要做的事情都做完了XD
那再來就是一行一行看程式碼。
use std::io;
前面已經解釋過,從 fn
開始:
fn main() -> io::Result<()> {
fn
宣告一個函數,而 main 這個 Rust 程式的入口點。->
後宣告這個函數會回傳的型別。io::Result
代表 I/O 操作可能發生錯誤的類型。()
代表成功時不會有回傳值。let mut input = String::new();
宣告一個可變的變數 input
並且宣告了類型為 String 同時賦予一個初始值為空字串。
在 Rust 中,變數必須有初始值,不能宣告一個沒有初始值的變數。
Rust 預設的變數是不可變的,因為我們要用 input
去接 terminal 數入的值,所以需要加上關鍵字 mut
(mutable) 明確告訴編譯器這個變數的值是可以改變的。
io::stdin().read_line(&mut input)?;
Rust 支援方法鏈結(Chaining methods),展現它 Functional Programming 的特性,我們可以在單一語句把多個函數串在一起,讓 code 更簡潔以及提升可讀性。
io::stdin()
是io
底下的一個關聯函數(Associated function),這邊會建構並返回 Stdin
的實例(instance)。read_line
。&mut input
代表傳給 read_line 一個可變的參考(reference),並且看 read_line
的提示可以看到這個參考型別必須要是 String。?
運算子:可以想像成一個「如果出錯就立刻停止並返回錯誤」的標誌,適合用來簡單的 error handling:
read_line
返回 Err
,?
運算子會直接將這個錯誤向上傳遞,使得整個 main
函式返回 Err
。read_line
返回 Ok
,?
運算子會提取出 Ok
中的值,並繼續執行後續的程式碼。println!("You typed: {}", input.trim());
:
println!
:println
的作用是把,內容輸出到 standard output ,有另一個類似的 print
也是一樣的作用,差別在於 println
會換行(最後面會塞換行符\n
),所以普遍會用 println
。至於後面的 !
其實因為 println
不是函數,而是 巨集(Macro)
,目前先不去探究這是什麼。"You typed: {}"
就是 Rust format string 的寫法,會把後面的參數依序帶入大括號中。input.trim()
: input
是一個字串,呼叫 trim
方法把前後空白字元移除。Ok(())
:
表示 main
function 執行成功,特別注意這邊句尾沒有分號,表示Ok(())
的值會被作為函數的返回值回傳,細節之後再來研究,目前知道在 Rust 有沒有分號是有差別的就好。
因為這個 function 我們不用回傳有意義的值,所以最後回傳Ok(())
顯式表達函數執行成功,如果本來就需要回傳值的話就不用特別用 Ok(())
。
最後一樣執行 cargo run
確認沒有地方寫錯
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/echo`
apple
You typed: apple
沒想到小小一段程式碼就有這麼多要解釋的,當然其中還有很多還不了解的細節,就放到後面再來搞懂囉💪