函数式编程
函数式编程
函数
函数是一组一起执行一个任务的语句块。每个 Rust 程序都至少有一个函数,即主函数 main()。划分的标准是每个函数执行一个单一的任务。这也是软件设计中经常说的 单一职责。这会让你的代码可读性更好。
定义与调用
定义语法
函数名称的命名规则和变量的命名规则一致。
参数用于将值传递给函数内部的语句。参数是可选的。
函数(function)使用 fn 关键字来声明。函数的参数需要标注类型,就和变量一样,如果函数返回一个值,返回类型必须在箭头 -> 之后指定。函数最后的表达式将作为返回值。也可以在函数内使用 return 语句来提前返一个值,甚至可以在循环或 if 内部使用。
fn 函数名称([参数:数据类型]) -> 返回值 { // fn 关键字,是function 的缩写
// 函数体
}
示例
fn hello() {
println!("Hello, rust!");
}
如果函数定义没有参数,那么参数是可以省略的。
调用语法
函数需要调用才会被执行,否则就是没用的,多余的代码。
fn main() {
hello();
}
// 输出 Hello, rust!
在 main()函数中调用 hello()函数。
函数返回值
不返回
一个 “不” 返回值的函数。实际上会返回一个单元类型 ()。当函数返回 () 时,函数签名可以省略返回类型。
函数返回值
函数在代码执行完成后,除了将控制权还给调用者之外,还可以携带值给它的调用者。函数可以返回值给它的调用者。称为 函数返回值。
Rust 语言的返回值定义语法,在 小括号后面使用 箭头 ( -> ) 加上数据类型 来定义的。 有 return
fn 函数名称() -> 返回类型 {
return 返回值;
}
隐式返回值
如果函数代码中没有使用 return 关键字,且最后一行没有分号,那么函数会默认使用最后一条语句的执行结果作为返回值。
这种设计有点类似于js的高级循环函数(map、filter等),可以当作一种特殊的语法糖
fn 函数名称() -> 返回类型 {
// 业务逻辑
返回值 // 没有分号则表示返回值
}
注:最后一条语句的执行结果,必须和函数定义时的返回数据类型一样,不然会编译会出错 。
fn get_name() -> String {
return String::from("Go语言微服务架构核心22讲"); // 显式返回值
}
fn get_name2() -> String {
String::from("从0到Go语言微服务架构师") // 隐式返回值
}
fn main() {
println!("r1:{}", get_name()); // 输出 r1:Go语言微服务架构核心22讲
println!("r2:{}", get_name2()); // 输出 r2:从0到Go语言微服务架构师
}
函数参数
函数参数 是一种将外部变量和值带给函数内部代码的一种机制。
- 把函数定义时指定的参数名叫做 形参
- 把函数调用时传递给函数的值叫做 实参
传递的 实参 数量与 形参 数量和类型必须相同。
值传递
值传递 是把传递的变量的值传递给函数的 形参,所以,函数体外的变量值和函数参数是各自保存了相同的值,互不影响。因此函数内部修改函数参数的值并不会影响外部变量的值。
fn double_price(mut price:i32){
price = price*2;
println!("内部的price是{}",price)
}
fn main() {
let mut price = 99;
double_price(price); // 输出 内部的price是198
println!("外部的price是{}", price); // 输出 外部的price是99
}
引用传递
值传递变量导致重新创建一个变量。但引用传递则不会,引用传递把当前变量的内存位置传递给函数。传递的变量和函数参数都共同指向了同一个内存位置。引用传递在参数类型的前面加上 & 符号。
fn 函数名称(参数: &数据类型) { // 但其实这里的 `&` 相当于Cpp语言中的 `*` 而非 `&`,太容易混淆了
// 执行逻辑代码
}
示例
fn double_price2(price:&mut i32){
*price = *price * 2;
println!("内部的price是{}", price)
}
fn main() {
let mut price = 88;
double_price2(&mut price); // 输出 内部的price是176
println!("外部的price是{}", price); // 输出 外部的price是176
}
星号*
用于访问变量 price 指向的内存位置上存储的变量的值。这种操作也称为 解引用。 因此 星号(*) 也称为 解引用操作符。
复合类型传参
对于复合类型,比如字符串,如果按照普通的方法传递给函数后,那么该变量将不可再访问。
fn show_name(name:String){
println!("充电科目 :{}", name);
}
fn main() {
let name:String = String::from("从0到Go语言微服务架构师");
show_name(name);
println!("调用show_name函数后: {}", name);
}
// 报错如下。这是一个很经典的所有权相关的报错,因为name的所有权已经移动到show_name里面了,外部的name就没有所有权了
error[E0382]: borrow of moved value: `name`
let name:String = String::from("从0到Go语言微服务架构师");
|---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
|show_name(name);
| ---- value moved here // 这里告诉你所有权已经移动到这个函数里了
|println!("调用show_name函数后: {}",name);
| ^^^^ value borrowed here after move
至于解决和更详细的原因,见所有权章节