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 的。