Rust 是一門多範式的編程語言,但 Rust 的編程風格是更偏向於函數式的,函數在 Rust 中是「一等公民」。這意味着,函數是能夠做爲數據在程序中進行傳遞。express
跟 C/C++/Go 同樣,Rust 程序也有一個惟一的程序入口 main 函數,以前在 hello world 例子中已經見到過了:編程
fn main() { }
Rust 使用 fn 關鍵字來聲明和定義函數,後面跟着函數名,一對括號是由於這函數沒有參數,而後是一對大括號表明函數體。下面是一個叫作 foo 的函數:數組
fn foo() { }
若是函數有返回值,在括號後面加上箭頭 -> ,接着是返回類型:安全
fn main() { println!("{:?}", add(1, 2)); } fn add(x: i32, y: i32) -> i32 { x + y }
add 函數能夠將兩個數值加起來並將結果返回。數據結構
Rust 主要是一個基於表達式的語言。只有兩種語句,其餘的一切都是表達式。閉包
這又有什麼區別的?表達是返回一個值,而語句不是。這就是爲甚惡魔這裏咱們以「不是全部控制路勁都返回一個值」結束:x + 1; 語句不返回一個值。Rust 中有兩種類型的語句:「聲明語句」和「表達式語句」。其他一切都是表達式。編程語言
在一些語言中,變量綁定能夠被寫成一個表達式,不只僅是語句。例如 Ruby:ide
x = y = 5
然而,在 Rust 中,使用 let 引入一個綁定並非一個表達式。下面的代碼會產生一個編譯時錯誤:函數
let x = (let y = 5); Blocking waiting for file lock on build directory Compiling hello_world v0.1.0 (yourpath/hello_world) error: expected expression, found statement (`let`) --> main.rs:2:14 | 2 | let x = (let y = 5); | ^^^ | = note: variable declaration using `let` is a statement error: aborting due to previous error error: Could not compile `hello_world`.
編譯器告訴咱們這裏它指望看到表達式的開頭,而 let 只能開始一個語句,不是一個表達式。ui
注意賦值一個已經綁定過的變量(例如 y = 5)仍然時一個表達式,即便它的(返回)值並非特別有用。不像其餘語言中賦值語句返回它賦的值(例如,前面例子中的 5 ),在 Rust 中賦值的值是一個空元組 ():
let mut y = 5; let x = (y = 6); // x has the value `()`, not `6`
表達式語句的目的是把任何表達式變爲語句。在實踐中,Rust 語法指望語句後面跟其餘語句。這意味着用分號來分隔各個表達式。着意味着 Rust 看起來很像大部分其餘使用分號做爲語句結尾的語言,而且你會看到分號出如今幾乎每一行 Rust 代碼。
那麼咱們說「幾乎」的例外是什麼呢?好比:
fn add_one(x: i32) -> i32 { x + 1; }
咱們的函數聲稱它返回一個 i32,不過帶有一個分號的話,它將返回一個 ()。Rust 意識到這可能不是咱們想要的,並在咱們看到的錯誤中建議咱們去掉分號:
Compiling hello_world v0.1.0 (yourpath/hello_world) error[E0269]: not all control paths return a value --> main.rs:1:1 | 1 | fn add_one(x: i32) -> i32 { | ^ | help: consider removing this semicolon: --> main.rs:2:10 | 2 | x + 1; | ^ error: aborting due to previous error error: Could not compile `hello_world`. fn add_one(x: i32) -> i32 { x + 1 }
Rust 的函數參數聲明和通常的變量聲明很是類似:參數名加上冒號再加上參數類型,不過不須要 let 關鍵字。例如這個程序將兩個數相加並打印結果:
fn main() { print_sum(5, 6); } fn print_sum(x: i32, y: i32) { println!("sum is: {}", x + y); }
在調用函數和聲明函數時,多個參數須要用逗號 , 分隔。與 let 不一樣的是,普通變量聲明能夠省略變量類型,而函數參數聲明則不能省略參數類型。下面的代碼不可以工做:
fn main() { print_sum(5, 6); } fn print_sum(x, y) { println!("sum is: {}", x + y); }
編譯器將會給出如下錯誤:
Compiling hello_world v0.1.0 (yourpath/hello_world) error: expected one of `:` or `@`, found `,` --> main.rs:5:15 | 5 | fn print_sum(x, y) { | ^ error: expected one of `:` or `@`, found `)` --> main.rs:5:18 | 5 | fn print_sum(x, y) { | ^ error[E0425]: unresolved name `x` --> main.rs:6:28 | 6 | println!("sum is: {}", x + y); | ^ <std macros>:2:27: 2:58 note: in this expansion of format_args! <std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>) main.rs:6:5: 6:35 note: in this expansion of println! (defined in <std macros>) error[E0425]: unresolved name `y` --> main.rs:6:32 | 6 | println!("sum is: {}", x + y); | ^ <std macros>:2:27: 2:58 note: in this expansion of format_args! <std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>) main.rs:6:5: 6:35 note: in this expansion of println! (defined in <std macros>) error[E0061]: this function takes 0 parameters but 2 parameters were supplied --> main.rs:2:5 | 2 | print_sum(5, 6); | ^^^^^^^^^^^^^^^ expected 0 parameters error: aborting due to previous error error: Could not compile `hello_world`.
這是一個有意而爲之的設計決定。即便像 Haskkell 這樣的可以全程序推斷的語言,註明類型也常常做爲一個最佳實踐被建議。不過,還有一種特殊的函數,閉包(closure, lambda),類型標註是可選的。將在後續章節中介紹閉包。
在 Rust 中,函數是一等公民(能夠存儲在變量/數據結構、能夠做爲參數傳入函數、能夠做爲返回值),因此函數參數不只能夠是通常類型,也能夠是函數。如:
fn main() { let xm = "xiaoming"; let xh = "xiaohong"; say_what(xm, hi); say_what(xh, hello); } fn hi(name: &str) { println!("Hi, {}.", name); } fn hello(name: &str) { println!("Hello, {}.", name); } fn say_what(name: &str, func: fn(&str)) { func(name) }
這個例子中,hi 函數和 hello 函數都只有一個 &str 類型的參數並且沒有返回值。say_what 函數有兩個參數,一個是 &str 類型,另外一個是函數類型(function type),它只有一個 &str 類型的參數且沒有返回值的行數類型。
又一次提到了模式,模式匹配給 Rust 增添了許多靈活性。模式匹配不只能夠用在變量申明中,也能夠用在函數參數申明中,如:
fn main() { let xm = ("xiaoming", 54); let xh = ("xiaohong", 66); print_id(xm); print_id(xh); print_name(xm); print_age(xh); print_name(xm); print_age(xh); } fn print_id((name, age): (&str, i32)) { println!("I'm {},age {}.", name, age); } fn print_age((_, age): (&str, i32)) { println!("My age is {}", age); } fn print_name((name,_): (&str, i32)) { println!("I am {}", name); }
這是一個元組(Tuple)匹配的例子,固然也能夠是其餘能夠在 let 語句中使用的類型。參數的匹配跟 let 語句的匹配同樣,也可使用下劃線表示丟棄一個值。
在 Rust 中,任何函數都有放回類型,當函數返回時,會返回一個該類型的值。再來看看 main 函數:
fn main() { }
函數的返回值類型是在參數列表後,加上箭頭 -> 和類型來指定的。不過,通常咱們看到的 main 函數的定義並無這麼作。這是由於 'main' 函數的返回值是 (), 在 Rust 中,當一個函數返回 () 時,能夠省略。main 函數的完整形式以下:
fn main() -> () { }
main 函數的返回值類型是 (), 它是一個特殊的元組——一個沒有元素的元組,稱之爲 unit,它表示一個函數沒有任何信息須要返回。() 類型,相似於 C/C++、Java、C# 中的 void 類型。
下面看一個有返回值的例子:
fn main() { let a = 123; println!("{}", inc(a)); } fn inc(n: i32) -> i32 { n + 1 }
這個例子中,inc 函數有一個 i32 類型的參數和返回值,做用是將參數加 1 返回。須要注意的是 inc 函數中只有一個 n + 1 一個表達式,並無像 C/C++ 等語言有顯示的 return 語句返回一個值。這是由於,與其餘語言不一樣,Rust 是基於表達式的語言,函數中最後一個表達式的值,默認做爲返回值(注意:沒有分號 :)。稍後會介紹語句和表達式。
Rust 也有 return 關鍵字,不過通常用於提前返回。看這個例子:
fn main() { let a = [1, 2, 3, 4, 8, 9]; println!("There is 7 in the array: {}", find(7, &a)); println!("There is 8 in the array: {}", find(8, &a)); } fn find(n: i32, a: &[i32]) -> bool { for i in a { if *i == n { return true; } } false }
find 函數接受一個 i32 類型 n 和一個 i32 類型的切片(slice)a ,返回一個 bool 值,若 n 是 a 的元素,則返回 'true',不然返回 false。能夠看到,return 關鍵字,用在 for 循環 if 表達式中,若此時 a 的元素與 n 相等,則馬上返回 true,剩下的循環沒必要再進行,不然一直循環檢測完整個切片(slice),最後返回 false。
不過,return 語句也能夠用在最後返回,把 find 函數最後一句 false 改成 return false; (注意分號不能夠省略)也是能夠的:
fn main() { let a = [1, 2, 3, 4, 8, 9]; println!("There is 7 in the array: {}", find(7, &a)); println!("There is 8 in the array: {}", find(8, &a)); } fn find(n: i32, a: &[i32]) -> bool { for i in a { if *i == n { return true; } } return false; }
不過這是一個糟糕的風格,不是 Rust 的編程風格了。
須要注意的是,for 循環中的 i ,其類型爲 &i32,須要使用解引用來變換爲 i32 類型。切片(slice)能夠看做是對數組的引用,後面的章節中會詳細介紹切片(slice)。
Rust 函數不支持多個返回值,可是咱們能夠利用元組返回多個值,配合 Rust 的模式匹配,使用起來十分靈活。好比這個例子:
fn mian() { let (p2, p3) = pow_2_3(456); println!("pow 2 of 456 is {}.", p2); println!("pow 3 of 456 is {}.", p3); } fn pow_2_3(n: i32) -> (i32, i32) { (n * n, n * n * n) }
這個例子中,pow_2_3 函數接收一個 i32 類型的值,返回其二次方和三次方的值,這兩個數值包裝再一個元組中返回。在 main 函數中,let 語句就可使用模式匹配將函數返回的元組進行解構,將這兩個返回值分別賦給 p2 和 p3, 從而能夠獲得 456 的二次方和三次方的值。
函數做爲返回值,其聲明與普通函數的返回類型申明同樣:
fn main() { let a = [1,2,3,4,5,6,7]; let mut b = Vec::<i32>::new(); for i in &a { b.push(get_func(*i)(*i)); } println!("{:?}", b); } fn get_func(n: i32) -> fn(i32) -> i32 { fn inc(n: i32) -> i32 { n + 1 } fn dec(n: i32) -> i32 { n - 1 } if n % 2 == 0 { inc } else { dec } }
這個例子中,get_func 函數接收一個 i32 類型的參數,返回一個類型爲 fn(i32) -> i32 的函數,若傳入的參數爲偶數,返回 inc ,不然返回 dec。這裏須要注意的是, inc 函數和 'dec' 函數的定義在 get_func 內,在函數內定義函數在不少其餘語言中是不支持的,不過 Rust 支持,這也是 Rust 靈活強大的體現。不過,在函數中定義的函數,不能包含函數中的變量,若要包含,應該使用閉包。因此:
fn main() { let f = get_func(); println!("{}", f(3)); } fn get_func() -> fn(i32)->i32 { let a = 1; fn inc(n:i32) -> i32 { n + a } inc }
這個例子會編譯出錯:
Compiling hello_world v0.1.0 (yourpath/hello_world) error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead --> main.rs:9:9 | 9 | n + a | ^ error: aborting due to previous error error: Could not compile `hello_world`.
若是改爲閉包的話是能夠的:
fn main() { let f = get_func(); println!("{}", f(3)); } fn get_func() -> Box<Fn(i32) -> i32> { let a = 1; let inc = move |n| n + a; Box::new(inc) }
後續會詳細介紹閉包。
發散函數是 Rust 中的一個特性。發散函數並不返回,它使用感嘆號 ! 做爲返回類型表示。
fn diverges() -> ! { panic!("This function never returns!"); }
panic!() 是一個宏,相似咱們已經見過的 println!()。與 println!() 不一樣的是,panic!() 致使當前執行的線程崩潰並返回指定的信息。由於這個函數會崩潰,因此它不會返回,因此它擁有一個類型 !,它表明「發散」。
若是你添加一個叫作 diverges() 的函數並運行:
fn main() { println!("hello"); diverging(); println!("world"); } fn diverging() -> ! { panic!("This function will never return"); }
因爲發散函數不返回,因此就算其後再有其餘語句也是不會執行的。若是後面還有其餘語句,會出現如下編譯警告:
Compiling hello_world v0.1.0 (yourpath/hello_world) warning: unreachable statement, #[warn(unreachable_code)] on by default --> main.rs:4:3 | 4 | println!("world"); | ^^^^^^^^^^^^^^^^^^ | = note: this error originates in a macro outside of the current crate
若是運行這個程序的化:
Running `yourpath\hello_world\target\debug\hello_world.exe` hello thread 'main' panicked at 'This function will never return', main.rs:8 note: Run with `RUST_BACKTRACE=1` for a backtrace. error: process didn't exit successfully: `yourpath\hello_world\target\debug\hello_world.exe` (exit code: 101)
發散函數能夠被用做任何類型:
fn diverges() -> ! { panic!("This function never returns!"); } let x: i32 = diverges(); let x: String = diverges();
'fn' 關鍵字能夠用來定義函數,除此以外,它還能夠用來構造函數類型。與函數定義主要的不一樣是,構造函數類型不須要函數名、參數名和函數體。
fn inc(n: i32) -> i32 { n + 1 } type IncType = fn(i32) -> i32; fn mian() { let func: IncType = inc; println!("3 + 1 = {}", func(3)); }
這個例子中使用 'fn' 定義了 inc 函數,它有一個 'i32' 類型的參數,返回 i32 類型的值。而後再用 fn 定義了一個函數類型,這個函數類型有 i32 類型的參數和返回值,並用 type 關鍵字定義了它的別名 IncType。在 main 函數中定義了一個變量 func, 其類型爲 IncType,並賦值爲 inc 而後在 println 宏中調用:func(3)。能夠看到,inc 函數的類型其實就是 IncType。
這裏有一個問題,咱們將 inc 賦值給了 func ,而不是 &inc ,這樣將 inc 函數的全部權轉移給了 func 嗎?賦值後還能夠以 inc() 形式調用 inc 函數嗎?看這個例子:
fn main() { let func: IncType = inc; println!("3 + 1 = {}", func(3)); println!("3 + 1 = {}", inc(3)); } type IncType = fn(i32) -> i32; fn inc(n: i32) -> i32 { n + 1 } Running `yourpath\hello_world\target\debug\hello_world` 3 + 1 = 4 3 + 1 = 4
這說明,賦值時, inc 函數的全部權並無被轉移到 func 變量上,而更像不可變引用。在 Rust 中,函數的全部權是不能轉移的的,咱們給函數類型的變量賦值是,賦給的通常是函數的指針,因此 Rust 中的函數類型,就像是 C/C++ 中的函數指針,固然, Rust 的函數類型更安全。可見,Rust 的函數類型,其實應該屬於指針類型(Pointer Type)。Rust 中的指針類型有兩種,一種爲引用,另外一種爲原始指針。Rust 中的函數類型是引用類型,由於它是安全的,而原始指針是不安全的,要使用原始指針,必須使用 unsafe 關鍵字申明。
正由於函數類型屬於指針類型,因此函數是能夠做爲數據在程序中進行傳遞。
fn one(n: i32) -> i32 { n + 1 } fn two(n: i32) -> i32 { n + 2 } fn three(n: i32) -> i32 { n + 3 } fn main() { let f1: fn(i32) -> i32 = one; let f2: fn(i32) -> i32 = two; let f3: fn(i32) -> i32 = three; let funcs = [f1, f2, f3]; for f in &funcs { println!("{:?}", f(1)); } }
咱們將三個類型爲 fn(i32) -> i32 的函數放到了一個數組中,而且去遍歷執行。要注意 for f in &fnucs 這裏,爲何要用引用呢?若是去掉 &:
Compiling hello_world v0.1.0 (yourpath/hello_world) error[E0277]: the trait bound `[fn(i32) -> i32; 3]: std::iter::Iterator` is not satisfied --> main.rs:21:5 | 21 | for f in funcs { | ^ trait `[fn(i32) -> i32; 3]: std::iter::Iterator` not satisfied | = note: `[fn(i32) -> i32; 3]` is not an iterator; maybe try calling `.iter()` or a similar method = note: required by `std::iter::IntoIterator::into_iter` error: aborting due to previous error error: Could not compile `hello_world`.
是由於數組沒有實現 std::iter::Iterator 這個 trait, 而切片(Slices)是一個數組的引用,切片(Slices)是實現了 std::iter::Iterator 的。