iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
Software Development

螃蟹幼幼班:Rust 入門指南系列 第 3

Day3 - 從 Echo Function 接觸基本概念

  • 分享至 

  • xImage
  •  

引言

根據之前接觸其他程式語言的經驗,在已經熟悉其中一種的情況下,其實可以直接看別的語言簡單的函數(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 庫的路徑結構

有點好奇所以順便找一下 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 開始:

  1. fn main() -> io::Result<()> {

    • fn 宣告一個函數,而 main 這個 Rust 程式的入口點。
    • -> 後宣告這個函數會回傳的型別。
    • io::Result 代表 I/O 操作可能發生錯誤的類型。
    • () 代表成功時不會有回傳值。
  2. let mut input = String::new();
    宣告一個可變的變數 input 並且宣告了類型為 String 同時賦予一個初始值為空字串。
    在 Rust 中,變數必須有初始值,不能宣告一個沒有初始值的變數。
    Rust 預設的變數是不可變的,因為我們要用 input 去接 terminal 數入的值,所以需要加上關鍵字 mut (mutable) 明確告訴編譯器這個變數的值是可以改變的。

  3. 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 中的值,並繼續執行後續的程式碼。
  4. println!("You typed: {}", input.trim());

    • println!println 的作用是把,內容輸出到 standard output ,有另一個類似的 print 也是一樣的作用,差別在於 println 會換行(最後面會塞換行符\n),所以普遍會用 println。至於後面的 ! 其實因為 println 不是函數,而是 巨集(Macro) ,目前先不去探究這是什麼。
    • "You typed: {}" 就是 Rust format string 的寫法,會把後面的參數依序帶入大括號中。
    • input.trim()input 是一個字串,呼叫 trim 方法把前後空白字元移除。
  5. 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

沒想到小小一段程式碼就有這麼多要解釋的,當然其中還有很多還不了解的細節,就放到後面再來搞懂囉💪


上一篇
Day2 - Hello world! Hello cargo!
下一篇
Day4 - 型別:整數
系列文
螃蟹幼幼班:Rust 入門指南25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言