Rust 入門之函數和註釋實例詳解

寫在前面

今天我們來學習 Rust 中的函數,最後會捎帶介紹一下如何在 Rust 中寫註釋。也是比較輕量級的一節,大傢快速過一下即可。

函數

函數本身是各個語言都支持的類型,我們此前已經多次使用 fn main() 這個函數來承載業務邏輯,fn 可以用來聲明一個函數,而 main 函數跟其他語言一樣,可以理解為程序啟動的【起點】,一切邏輯從這裡開始。

Rust 本身的命名規范是【snake case】,即下劃線 + 小寫,這個其實各個語言都有自己規范,分清楚環境即可。

fn main() {
    println!("Hello, world!");
    another_function();
}
fn another_function() {
    println!("Another function.");
}

這裡的 another_function 就是個沒有入參,沒有出參的函數,命名遵循 snake case,很好理解。

Rust 中的函數跟其他語言是一樣的,用 fn 來聲明,後面加上函數名,小括號裡面可以放入參,之後可以定義出參,最後用花括號來承載函數體。調用函數也不復雜,函數名後面跟上小括號+參數即可,註意 scope 就行,這裡是因為我們的 another_function 就在當前包下,所以直接就那來調用。調用的時候要保證【函數所在的 scope 是對 caller 可見】的即可。

我們在 rust-learn 項目下通過 cargo new functions 新建一個項目,試一下上面的代碼:

$ cargo run
===============================
   Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.76s
     Running `target/debug/functions`
Hello, world!
Another function.

另外需要強調一點,Rust 文件內函數定義並不要求順序,隻要定義在 scope 內就能解析,比如 main 函數先於 another_function 定義是沒問題的。

參數

還是基於我們此前的 another_function,我們嘗試加一下入參,看看應該怎麼做:

fn main() {
    another_function(5);
}
fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

執行過後,結果如下:

$ cargo run
===============================
   Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s
     Running `target/debug/functions`
The value of x is: 5

此時 another_function 增加瞭一個參數 x,我們聲明其類型為 i32。在 main 函數中調用的時候,傳入我們的參數 5,最後被打印出來。

可能有的地方會特意提一下這兩個概念:

  • 形參:是在定義函數時使用的參數,目的是用來接收調用該函數時傳進來的實際參數,即 parameter;
  • 實參:是在調用時傳遞給函數的參數,即 arguments。

但通常說起來的時候我們不太區分,對我們來說統一叫【參數】即可。

上面示例中,我們定義入參是這樣的:fn another_function(x: i32)

這裡【冒號 + 空格 + 類型】的寫法我們已經見過很多次瞭,那能不能不帶類型呢?我直接寫個 fn another_function(x),具體格式留給編譯器來推斷 ok 不?

在 Rust 中這件事是不 ok的,按照規范,對於每個入參你都必須清晰地指明【類型】,這樣編譯器也省事,報錯時也能更精準給出相關判斷。如果我們需要多個入參,用【逗號】分隔即可:

fn main() {
    print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

執行結果如下:

$ cargo run
========================
  Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.87s
     Running `target/debug/functions`
The measurement is: 5h

語句和表達式

Rust 本身是一個基於表達式的語言,所以這兩個概念我們先區分一下,語句(statements),表達式(expressions)是什麼區別?

Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.

簡單說,就是看有沒有【返回值】,無返回值的是語句,有返回值的是表達式,表達式可以是一個語句的組成部分。

舉個例子:let y = 6; 這就是一個【語句】,而 6 就是一個【表達式】,在 Rust 中你是不能做 let x = (let y = 6); 這樣的操作的,因為括號裡面的部分是個語句,語句沒有返回值,那麼該拿什麼給 x 賦值呢?

所以,不像其他語言,可能允許類似 x = y = 6,這樣讓 x 和 y 都賦值瞭 6。Rust 是不允許這樣的。

fn main() {
    let y = {
        let x = 3;
        x + 1
    };
    println!("The value of y is: {y}");
}

比如上面這個案例,在花括號這個 scope 中,我們定義瞭 x 變量,將其賦值為 3,然後將 x+1 這個表達式返回,所以 y 被賦值為 4。

花括號裡面的部分就是一個表達式,返回瞭 4 。註意 x + 1 的結尾沒有分號,這也是表達式的特征。這裡千萬不能加分號,要想清楚。如果你想用一個表達式返回,就不加分號。加瞭之後變成瞭語句,但也不會返回什麼東西。

Rust 函數體則是由一系列【語句】+ 默認可選的一個【表達式】組成。為什麼是可選的?因為類似我們前面的函數,沒有返回值,不需要最後的這個【表達式】。

返回值

Rust 是不支持命名返回值的(這一點跟 Golang 有所不同),函數定義出參的部分需要用【箭頭符號】顯式地聲明。

不像很多函數要求顯式的 return 返回值,Rust 默認會返回最後的表達式的值。當然我們如果想 early return 也是 ok的,但大多數函數不會寫 return 這個關鍵字,而是隱式地返回最後一個表達式。我們來看一個例子:

fn five() -> i32 {
    5
}
fn main() {
    let x = five();
    println!("The value of x is: {x}");
}

這裡的 five 函數非常簡單,隻有一個 5 作為表達式返回,不需要 return。

這是完全合法的 Rust 函數,出參隻有一個 i32。我們加上入參,再看一個例子:

fn main() {
    let x = plus_one(5);
    println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
    x + 1
}

此時我們有一個 i32 入參,也有一個 i32 出參,函數體是一個簡單的表達式 x + 1。運行上面代碼打印的結果是 The value of x is: 6,符合預期。

我們試試給 x + 1 後面加上個分號看看:

fn main() {
    let x = plus_one(5);
    println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
    x + 1;
}

此時運行結果果然報錯(這個不是運行時報錯,是編譯階段識別的)

$ cargo run
====================
   Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon
For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` due to previous error

問題在於,plus_one 說瞭會有返回值 i32,但到最後也沒發現【表達式】,此時 Rust 默認會返回 () 一個空的 tuple(我們上一節講過,這個叫 unit),所以報錯叫做【 mismatched types】,而不是類似【no return value】,這裡是不是就理解瞭?

沒有返回值的函數,本質上是返回瞭一個 unit:

// Functions that "don't" return a value, actually return the unit type `()`
fn fizzbuzz(n: u32) -> () {
    if is_divisible_by(n, 15) {
        println!("fizzbuzz");
    } else if is_divisible_by(n, 3) {
        println!("fizz");
    } else if is_divisible_by(n, 5) {
        println!("buzz");
    } else {
        println!("{}", n);
    }
}

問題又來瞭,那 Rust 能不能支持多個出參呢?類似 Golang 中的:

func addsub(x, y int) (int, int) {
    return x + y, x - y
}

其實 Rust 對這個事情的解決方案就是我們已經見過多次的 tuple:

fn addsub(x: isize, y: isize) -> (isize, isize) {
    (x + y, x - y)
}
fn my_func() -> (u8, bool) {
    (1, true)
}

圓括號千萬不能少,記住我們 return 的是個 tuple,不是多個單獨的值。

這裡有一個可運行的 online 示例,大傢可以復習一下 tuple,結合多個返回值體會一下:

fn swap(x: i32, y: i32) -> (i32, i32) {
    return (y, x);
}
fn main() {
    // return a tuple of return values
    let result = swap(123, 321);
    println!("{} {}", result.0, result.1);
    // destructure the tuple into two variables names
    let (a, b) = swap(result.0, result.1);
    println!("{} {}", a, b);
}

註釋

註釋其實比較簡單,我們快速提一下。

Rust 的行註釋就是常見的 // 雙斜杠,如果一行放不下,需要多行的話,也需要在每一行前面加.

fn main() {
    // I'm feeling lucky today
    let lucky_number = 7;
}

文檔註釋有些許的區別,這裡需要用 /// 三斜杠,這樣能夠輔助生成 HTML 文檔。

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

以上就是Rust 入門之函數和註釋實例詳解的詳細內容,更多關於Rust 函數註釋的資料請關註WalkonNet其它相關文章!

推薦閱讀: