【Rust】 4. 函数与闭包

发布于:2025-08-29 ⋅ 阅读:(11) ⋅ 点赞:(0)

第一部分:函数 (Functions)

  1. 函数简介

函数是 Rust 中封装可重用代码的基本构建块,使用 fn 关键字定义。

定义语法:


fn function_name(parameter1: Type1, parameter2: Type2, ...) -> ReturnType {
    // 函数体
    // 可以包含多条语句和表达式
    // 可以使用 return 语句返回值
}
  • fn:定义函数的关键字。

  • function_name:函数名,遵循 snake_case 命名规范(全小写,下划线分隔)。

  • parameter: Type:参数列表,每个参数都必须显式注明类型。

  • -> ReturnType:指定函数的返回类型。

  • 函数体:由 {} 包围,包含实际的执行逻辑。

核心特性:

  • 函数返回值可以通过 return 语句显式指定,也可以通过省略最后一个表达式末尾的分号来隐式返回。

  • 若未指定返回类型,编译器默认返回单元类型 ()。

  • 程序入口是 fn main()。

  • Rust 不关心函数定义的位置(可在 main 之前或之后),只要在作用域内定义了即可调用。

  • 函数体内可以定义其他项(如静态变量、常量、嵌套函数、trait、类型等)。

  1. 函数实例

fn main() {
    let num = add(1, 2); // 调用函数
    println!("{}", num) // 输出: 3
}

fn add(x: i32, y: i32) -> i32 {
    x + y // 这是一个表达式,其值被隐式返回
}
  1. 参数与返回值
  • 参数:函数可以接受零个或多个强类型参数。

  • 返回值:函数可以返回一个值,或不返回任何值(即返回 ())。

  • 语句 vs 表达式:这是理解返回值的核心。

    • 语句:以分号 ; 结尾,执行操作但不返回值(类型为 ())。
    • 表达式:不以分号结尾,会计算并产生一个值。

错误示例:


fn add(x: i32, y: i32) -> i32 {
    x + y; // 错误!加了分号,这变成了语句,返回的是 (),与声明的返回类型 i32 冲突
}
// 正确做法是去掉分号:`x + y`
  1. 参数传递方式

Rust 支持两种参数传递方式:

  • 按值传递 (Pass by Value)

    • 函数获得参数的副本。

    • 在函数内部修改副本不会影响原始值。

    • 适用于实现 Copy trait 的类型(如基本整数、布尔、字符等)。

  • 按引用传递 (Pass by Reference)

    • 函数获得参数的引用(指针),而非值本身。

    • 不可变引用 (&T):允许函数读取但不能修改原始值。

    • 可变引用 (&mut T):允许函数读取和修改原始值。

    • 适用于所有类型,尤其是不想转移所有权或数据较大时。

示例:


fn increment_by_value(mut num: i32) { // 按值传递,获取副本
    num += 1; // 修改副本
    println!("Result: {}", result); // 输出: 6
}

fn increment_by_reference(num: &mut i32) { // 按可变引用传递
    *num += 1; // 解引用并修改原始值
}

fn main() {
    let mut value = 5;

    increment_by_value(value); // 传递值的副本
    println!("Original value: {}", value); // 输出: 5 (原始值未变)

    increment_by_reference(&mut value); // 传递可变引用
    println!("Updated value: {}", value); // 输出: 6 (原始值被修改)
}
  1. 函数作为值与高阶函数 (Higher-Order Functions)

在 Rust 中,函数是一等公民(First-class citizens),可以像普通值一样被赋值、传递和返回。

  • 函数指针类型:fn(i32, i32) -> i32

  • 高阶函数:指那些以函数为参数和/或返回函数的函数。

示例:


fn add(a: i32, b: i32) -> i32 { a + b }
fn subtract(a: i32, b: i32) -> i32 { a - b }

// 高阶函数:接受一个函数指针 `operation` 作为参数
fn calculate(operation: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    operation(a, b) // 调用传入的函数
}

fn main() {
    let result1 = calculate(add, 5, 3); // 传递函数 `add`
    println!("Result1: {}", result1); // 输出: 8

    let result2 = calculate(subtract, 8, 4); // 传递函数 `subtract`
    println!("Result2: {}", result2); // 输出: 4
}
  1. 泛型函数 (Generic Functions)

泛型函数允许编写适用于多种类型的通用代码,使用类型参数(通常用 表示)实现。

示例:


use std::fmt::Display;

// <T: Display> 表示类型 T 必须实现 Display trait
fn print_type<T: Display>(value: T) {
    println!("Type: {}", std::any::type_name::<T>()); // 打印类型名
    println!("Value: {}", value); // 打印值
}

fn main() {
    print_type(5); // T 是 i32
    print_type("Hello"); // T 是 &str
}
// 输出:
// Type: i32
// Value: 5
// Type: &str
// Value: Hello
  1. 发散函数 (Diverging Functions)

发散函数永远不会返回到调用者。其返回类型标记为 !(never type)。

常见场景:

  • panic! 宏及其变体(如 unimplemented!, unreachable!)。

  • 无限循环 loop {}。

  • 进程退出函数(如 std::process::exit)。

示例:


fn diverges() -> ! {
    panic!("This function will never return!");
}

特点: 发散表达式可以被转换为任何类型,这在需要统一类型的场合(如 match 表达式的不同分支)很有用。
8. 常量函数 (const fn)

使用 const 关键字修饰的函数可以在编译时被求值,其结果可作为常量使用。

  • 限制:const fn 的函数体内只能使用有限的编译时可确定的操作。

  • 状态:此功能仍在发展和完善中。

示例:


const fn square(x: i32) -> i32 {
    x * x
}

const CONST_SQUARE: i32 = square(5); // 编译时计算

第二部分:闭包 (Closures)

  1. 闭包简介

闭包是匿名函数,可以捕获其定义所在作用域中的变量。它们非常灵活,可以像普通值一样传递和存储。
2. 基本语法与演变

闭包通常定义为 |args| expression 的形式。

演变过程:


// 1. 标准函数
fn add_one(x: i32) -> i32 { x + 1 }

// 2. 闭包 (完整写法)
let add_one_v1 = |x: i32| -> i32 { x + 1 };

// 3. 闭包 (省略返回类型,编译器推断)
let add_one_v2 = |x: i32| { x + 1 };

// 4. 闭包 (省略大括号,单表达式)
let add_one_v3 = |x| x + 1; // 类型也可由调用处推断

// 使用
println!("{}", add_one_v3(5)); // 输出: 6
  1. 捕获环境变量

这是闭包与函数最核心的区别:闭包可以捕获并使用其定义作用域内的变量。

闭包可以:


fn main() {
    let x = 10;
    let add = |y| x + y; // 捕获了外部变量 `x`
    println!("{}", add(5)); // 输出: 15
}

函数不可以:


fn main() {
    let x = 10;
    fn add(y: i32) -> i32 {
        x + y // 错误!函数无法捕获环境变量 `x`
    }
}
  1. 捕获方式与闭包 Trait

Rust 闭包根据其如何使用捕获的变量,会自动实现以下三种 Trait 之一:

Trait 捕获方式 是否可变 描述 示例
Fn 不可变借用(&T) 不可变 只读取捕获变量的值,不修改它们。最常见。 let c = |x| x + captured_var;
FnMut 可变借用(&mut T) 可变 可以修改捕获的变量。 let mut c = |x { *captured_var += x; };
FnOnce 获取所有权(T) 消耗 会消耗(移动)捕获的变量,只能调用一次。 let c = |x| { drop(captured_var); };
  • move 关键字:强制闭包通过值(获取所有权)来捕获变量,通常用于将闭包传递到新线程时避免生命周期问题。

    let s = String::from("hello");
    let f = move || println!("{}", s); // `s` 被移动进闭包,所有权不再在主函数中
    // println!("{}", s); // 错误!s 的所有权已移入闭包
    f();
  • 编译器会自动为闭包选择最合适的 Trait,实现静态分发。
  1. 闭包 vs 函数
项目 函数 闭包
是否有名字 有 (fn 定义) 无(匿名)
捕获外部变量 ❌ 不能 ✅ 可以
类型 函数指针 (fn()) 匿名结构体,实现 Fn/FnMut/FnOnce
类型标注 必须显式注明参数和返回类型 通常可省略,由编译器推断
存储 代码段 通常存储在栈上(可能捕获数据)

总结: 闭包提供了比函数更大的灵活性,特别是在需要捕获上下文和进行函数式编程的场景中。函数则更简单、更明确,适用于不需要捕获环境的通用逻辑。


网站公告

今日签到

点亮在社区的每一天
去签到