# 函數
> [functions.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/functions.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
到目前為止你應該見過一個函數,`main`函數:
~~~
fn main() {
}
~~~
這可能是最簡單的函數聲明。就像我們之前提到的,`fn`表示“這是一個函數”,后面跟著名字,一對括號因為這函數沒有參數,然后是一對大括號代表函數體。下面是一個叫`foo`的函數:
~~~
fn foo() {
}
~~~
那么有參數是什么樣的呢?下面這個函數打印一個數字:
~~~
fn print_number(x: i32) {
println!("x is: {}", x);
}
~~~
下面是一個使用了`print_number`函數的完整的程序:
~~~
fn main() {
print_number(5);
}
fn print_number(x: i32) {
println!("x is: {}", x);
}
~~~
如你所見,函數參數與`let`聲明非常相似:參數名加上冒號再加上參數類型。
下面是一個完整的程序,它將兩個數相加并打印結果:
~~~
fn main() {
print_sum(5, 6);
}
fn print_sum(x: i32, y: i32) {
println!("sum is: {}", x + y);
}
~~~
在調用函數和聲明函數時,你需要用逗號分隔多個參數。
與`let`不同,你*必須*為函數參數聲明類型。下面代碼將不能工作:
~~~
fn print_sum(x, y) {
println!("sum is: {}", x + y);
}
~~~
你會獲得如下錯誤:
~~~
expected one of `!`, `:`, or `@`, found `)`
fn print_number(x, y) {
~~~
這是一個有意為之的設計決定。即使像 Haskell 這樣的能夠全程序推斷的語言,注明類型也經常作為一個最佳實踐被建議。我們認為即使允許在在函數體中推斷,也要強制函數聲明參數類型。這是一個全推斷與無推斷的最佳平衡。
如果我們要一個返回值呢?下面這個函數給一個整數加一:
~~~
fn add_one(x: i32) -> i32 {
x + 1
}
~~~
Rust 函數只能返回一個值,并且你需要在一個“箭頭”后面聲明類型,它是一個破折號(`-`)后跟一個大于號(`>`)。
注意這里并沒有一個分號。如果你把它加上:
~~~
fn add_one(x: i32) -> i32 {
x + 1;
}
~~~
你將會得到一個錯誤:
~~~
error: not all control paths return a value
fn add_one(x: i32) -> i32 {
x + 1;
}
help: consider removing this semicolon:
x + 1;
^
~~~
這揭露了關于 Rust 兩個有趣的地方:它是一個基于表達式的語言,并且分號與其它基于“大括號和分號”的語言不同。這兩個方面是相關的。
### 表達式 VS 語句
Rust 主要是一個基于表達式的語言。只有兩種語句,其它的一切都是表達式。
然而這又有什么區別呢?表達式返回一個值,而語句不是。這就是為什么這里我們以“不是所有控制路徑都返回一個值”結束:`x + 1;`語句不返回一個值。Rust 中有兩種類型的語句:“聲明語句”和“表達式語句”。其余的一切是表達式。讓我們先討論下聲明語句。
在一些語言中,變量綁定可以被寫成一個表達式,不僅僅是語句。例如 Ruby:
~~~
x = y = 5
~~~
然而,在 Rust 中,使用`let`引入一個綁定并*不是*一個表達式。下面的代碼會產生一個編譯時錯誤:
~~~
let x = (let y = 5); // expected identifier, found keyword `let`
~~~
編譯器告訴我們這里它期望看到表達式的開頭,而`let`只能開始一個語句,不是一個表達式。
注意賦值一個已經綁定過的變量(例如,`y = 5`)仍是一個表達式,即使它的(返回)值并不是特別有用。不像其它語言中賦值語句返回它賦的值(例如,前面例子中的`5`),在 Rust 中賦值的值是一個空的元組`()`:
~~~
let mut y = 5;
let x = (y = 6); // x has the value `()`, not `6`
~~~
Rust中第二種語句是*表達式語句*。它的目的是把任何表達式變為語句。在實踐環境中,Rust 語法期望語句后跟其它語句。這意味著你用分號來分隔各個表達式。這意味著Rust看起來很像大部分其它使用分號做為語句結尾的語言,并且你會看到分號出現在幾乎每一行你看到的 Rust 代碼。
那么我們說“幾乎”的例外是神馬呢?你已經見過它了,在這些代碼中:
~~~
fn add_one(x: i32) -> i32 {
x + 1
}
~~~
我們的函數聲稱它返回一個`i32`,不過帶有一個分號,它會返回一個`()`。Rust意識到這可能不是我們想要的,并在我們之前看到的錯誤中建議我們去掉分號。
### 提早返回(Early returns)
不過提早返回怎么破?Rust確實有這么一個關鍵字,`return`:
~~~
fn foo(x: i32) -> i32 {
return x;
// we never run this code!
x + 1
}
~~~
使用`return`作為函數的最后一行是可行的,不過被認為是一個糟糕的風格:
~~~
fn foo(x: i32) -> i32 {
return x + 1;
}
~~~
如果你之前沒有使用過基于表達式的語言,那么前面的沒有`return`的定義可能看起來有點奇怪。不過它隨著時間的推移它會變得直觀。
### 發散函數(Diverging functions)
Rust有些特殊的語法叫“發散函數”,這些函數并不返回:
~~~
fn diverges() -> ! {
panic!("This function never returns!");
}
~~~
`panic!`是一個宏,類似我們已經見過的`println!()`。與`println!()`不同的是,`panic!()`導致當前的執行線程崩潰并返回指定的信息。因為這個函數會崩潰,所以它不會返回,所以它擁有一個類型`!`,它代表“發散”。
如果你添加一個叫做`diverges()`的函數并運行,你將會得到一些像這樣的輸出:
~~~
thread ‘’ panicked at ‘This function never returns!’, hello.rs:2
~~~
如果你想要更多信息,你可以設定`RUST_BACKTRACE`環境變量來獲取 backtrace :
~~~
$ RUST_BACKTRACE=1 ./diverges
thread '<main>' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
3: 0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
4: 0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
5: 0x7f4027738809 - diverges::h2266b4c4b850236beaa
6: 0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
7: 0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
8: 0x7f402773d1d8 - __rust_try
9: 0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
10: 0x7f4027738a19 - main
11: 0x7f402694ab44 - __libc_start_main
12: 0x7f40277386c8 - <unknown>
13: 0x0 - <unknown>
~~~
`RUST_BACKTRACE`也可以用于 Cargo 的`run`命令:
~~~
$ RUST_BACKTRACE=1 cargo run
Running `target/debug/diverges`
thread '<main>' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
3: 0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
4: 0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
5: 0x7f4027738809 - diverges::h2266b4c4b850236beaa
6: 0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
7: 0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
8: 0x7f402773d1d8 - __rust_try
9: 0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
10: 0x7f4027738a19 - main
11: 0x7f402694ab44 - __libc_start_main
12: 0x7f40277386c8 - <unknown>
13: 0x0 - <unknown>
~~~
發散函數可以被用作任何類型:
~~~
# fn diverges() -> ! {
# panic!("This function never returns!");
# }
let x: i32 = diverges();
let x: String = diverges();
~~~
### 函數指針
我們也可以創建指向函數的變量綁定:
~~~
let f: fn(i32) -> i32;
~~~
`f`是一個指向一個獲取`i32`作為參數并返回`i32`的函數的變量綁定。例如:
~~~
fn plus_one(i: i32) -> i32 {
i + 1
}
// without type inference
let f: fn(i32) -> i32 = plus_one;
// with type inference
let f = plus_one;
~~~
你可以用`f`來調用這個函數:
~~~
# fn plus_one(i: i32) -> i32 { i + 1 }
# let f = plus_one;
let six = f(5);
~~~
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照