跳至主要內容

函数式编程

LincZero大约 5 分钟

函数式编程

函数

函数是一组一起执行一个任务的语句块。每个 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

至于解决和更详细的原因,见所有权章节