Rust 函数

函数是组织代码的基本单位。Rust 的函数语法简单清晰,但有几个独特细节:用 fn 声明(不是 function 也不是 def)、参数必须显式标注类型、尾表达式即返回值(不加分号)、有"发散函数"这种特殊类型。这一篇我们把这些讲透。

1. 函数定义基础

Rust 用 fn 关键字定义函数。语法结构:

// 函数定义语法:
// fn 函数名(参数名: 类型, ...) -> 返回类型 { 函数体 }
fn add(a: i32, b: i32) -> i32 {
    a + b              // 尾表达式(无分号)= 返回值
}

fn greet() {           // 不写 -> 表示返回 () (unit)
    println!("你好");
}

fn main() {
    let sum = add(3, 5);
    println!("3 + 5 = {}", sum);    // 8
    greet();                        // 你好

    // 函数可以先使用后定义(Rust 不要求"先声明")
    let r = multiply(4, 6);
    println!("4 * 6 = {}", r);      // 24
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

几个要点:

2. 表达式函数体(关键)

这是 Rust 函数最重要的细节,上一篇语法已经讲过,这里再强调。函数体最后一行不加分号,就是返回值——这是 Rust 风格的写法:

// ===== 表达式函数体 vs 语句函数体 =====

// 推荐:用尾表达式返回值(无分号)
fn square(n: i32) -> i32 {
    n * n                  // 无分号,作为返回值
}

// 也可以用 return 显式返回(加分号)
fn square2(n: i32) -> i32 {
    return n * n;          // 显式 return
}

// 早期返回必须用 return
fn abs(n: i32) -> i32 {
    if n < 0 {
        return -n;         // 提前返回
    }
    n                       // 否则尾表达式返回
}

// 错误!尾表达式加了分号就变成语句,返回 () 而非 i32
// fn bad(n: i32) -> i32 {
//     n + 1;              // 编译错误!类型不匹配
// }

规则总结:

这是 Rust 新手最常遇到的编译错误之一:声明了返回类型,但尾表达式加了分号,导致返回 () 不匹配。养成"返回值那行不加分号"的习惯。

3. 多返回值(用元组)

Rust 不像 Go 那样支持 func f() (int, error) 多返回值,但可以用元组达到同样效果。配合解构,代码很清晰:

// Rust 不支持多返回值,但可以返回元组
fn divmod(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}

fn main() {
    let (quotient, remainder) = divmod(17, 5);
    println!("17 / 5 = {} 余 {}", quotient, remainder);   // 3 余 2

    // 或者用 .0 .1 访问
    let r = divmod(17, 5);
    println!("{} {}", r.0, r.1);

    // 需要返回多个不同类型的值?用元组或自定义结构体
    let (name, age, active) = get_user();
    println!("{} {} {}", name, age, active);
}

fn get_user() -> (String, u32, bool) {
    ("小明".to_string(), 20, true)
}

这是 Rust 处理"多个返回值"的标准方式。当你需要返回更多结构化数据时,定义一个 struct 通常比大元组更清晰。

4. 发散函数(never 类型)

Rust 有一个独特的返回类型 !(读作 never / 发散),表示"这个函数永远不会正常返回"——总是 panic、无限循环,或终止进程:

// 发散函数(diverging function):返回类型是 !
// 表示"这个函数永远不会正常返回"(总是 panic 或无限循环)

fn always_panic() -> ! {
    panic!("这个函数一定崩溃");
    // 不会执行到这里
}

fn infinite_loop() -> ! {
    loop {
        // 永远不退出
    }
}

// ! 类型可以转换为任何类型,因此很灵活
fn main() {
    let x: i32 = match Some(5) {
        Some(n) => n,
        None => always_panic(),    // ! 可以变成 i32
    };
    println!("{}", x);
}

// 常见的发散:process::exit、unreachable!、panic!、todo!()
fn not_done() -> i32 {
    todo!("还没实现")          // 调用时 panic,标注"待办"
}

发散函数的好处是 ! 可以自动转换为任何类型。这让 match 等场景下类型仍然一致——比如 None 分支调用 panic! 后,编译器知道不会真的返回值,类型检查照样通过。

常用的发散函数:panic!(崩溃)、todo!(标注"待实现",调到就崩)、unreachable!(理论上不该到这)、标准库 process 模块的 exit 函数(退出进程)。

5. 参数传递与所有权(预告)

函数传参在 Rust 里有个特殊行为——默认是按值传递且转移所有权。这意味着传给函数后,原变量失效(对 String 等堆类型)。但通常我们用引用(借用)来避免所有权转移:

fn main() {
    // 参数必须标注类型(不像 let 那样能推断)
    let r = add(1, 2);

    // 参数按值传递(默认 move 所有权,后面所有权篇详细讲)
    let s = String::from("hi");
    takes_string(s);
    // println!("{}", s);      // 编译错误!s 的所有权已转移

    // 想保留所有权:传引用(借用)
    let s2 = String::from("hi");
    borrow_string(&s2);         // 借用
    println!("还能用:{}", s2);  // OK
}

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

fn takes_string(s: String) {
    println!("拿到:{}", s);
}   // s 离开作用域,内存释放

fn borrow_string(s: &String) {
    println!("借用:{}", s);
}   // s 是引用,不释放原数据

这是 Rust 区别于所有语言的核心,后面所有权借用两篇会深入展开。这里先记住:函数参数可以传值(转移所有权),也可以传引用(借用,不转移)。

6. 闭包(Closure)

闭包是 Rust 的匿名函数,语法很简洁。它能捕获外部变量,是函数式编程和迭代器的利器:

fn main() {
    // 闭包:匿名函数,可以捕获外部变量
    // 语法:|参数| { 体 }   或   |参数| 表达式

    let add = |a, b| a + b;          // 类型推断
    println!("{}", add(1, 2));       // 3

    // 捕获外部变量
    let multiplier = 3;
    let times = |x| x * multiplier;  // 捕获了 multiplier
    println!("{}", times(5));        // 15

    // 多行闭包
    let calc = |a, b| {
        let sum = a + b;
        sum * 2
    };
    println!("{}", calc(3, 4));      // 14

    // 闭包常用于迭代器(下一篇会用)
    let nums = vec![1, 2, 3, 4];
    let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
    println!("{:?}", doubled);       // [2, 4, 6, 8]
}

闭包和普通函数的区别:

7. 函数命名约定

Rust 函数命名遵循蛇形命名法(snake_case):全小写,下划线分单词。如 calculate_totalread_to_stringis_empty。返回 bool 的函数习惯以 is_has_should_ 开头,如 is_empty()contains()

类型(结构体、枚举、Trait)用大驼峰:HttpClientUserConfig。这种命名约定让代码可读性强,rustfmt 和 clippy 会帮你检查。

8. 函数指针与高阶函数

Rust 函数是"一等公民",可以作为参数、返回值传递。函数本身有类型 fn(T) -> U(小写 fn 是函数指针类型)。常见用法是配合闭包,写接受函数的"高阶函数":mapfilterfold 等迭代器方法都是高阶函数。这在实际项目中非常常见。

小结

这一篇你掌握了 Rust 函数的全部要点:fn 声明、参数类型标注、尾表达式即返回值(无分号)、用元组做多返回值、! 发散类型、闭包语法。最该记住的两个细节:尾表达式不加分号(加了变语句返回 ())、参数默认转移所有权(用引用避免)。下一篇我们进入 Rust 的灵魂——所有权。理解了所有权,你就理解了 Rust。

← 上一篇 Rust 控制流

下一篇 Rust 所有权(重点)

✈️💬