首页 / Rust 入门教程 / 函数定义与参数

Rust 入门教程

函数定义与参数

本教程共 78 篇 · 第 13 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

RustRust 入门教程函数fn参数返回值return

本节目标:用 fn 定义自己的函数,掌握参数必须标类型、返回值用 -> 声明,以及 Rust 特色的”末行表达式即返回值”写法。

前面所有例子都挤在 main 里。真实代码要把逻辑拆成一个个函数,各自负责一件事,方便复用和测试。这一章讲怎么在 Rust 里定义函数、传参数、拿返回值。

13-1 用 fn 定义函数

fn 是 function 的缩写。一个最简单的函数:

fn greet() {
    println!("你好!");
}

fn main() {
    greet();
}

fn greet() 定义了名为 greet 的函数,括号里为空表示不接收参数,函数体打印一句问候。main 里调用 greet() 才会执行它。函数定义顺序不重要,不一定要写在调用之前。

Note

函数名惯例用 snake_case(全小写加下划线),比如 calculate_pricesend_email。Rust 编译器甚至会建议你改掉 camelCase 的命名,保持风格统一。

13-2 参数必须标注类型

Rust 要求每个参数都必须显式写类型,不能靠推断。这是刻意的:函数签名是你和编译器、和别的程序员之间的契约,类型写死才清晰。

fn greet(name: &str, age: u32) {
    println!("我叫 {name},今年 {age} 岁");
}

fn main() {
    greet("小明", 18);
}

name: &str 表示参数 name 是字符串切片(后面章节细讲),age: u32 是无符号 32 位整数。调用时按位置传值。

Warning

新手常写 fn add(x, y) 然后困惑为什么报错。Rust 不允许参数省略类型,必须写成 fn add(x: i32, y: i32)。这点和 TypeScript 可选标注、Python 完全不标都不一样,是 Rust 的硬性规定。

13-3 声明返回值类型

如果函数要返回一个值,用箭头 -> 在参数括号后声明返回类型:

fn add(x: i32, y: i32) -> i32 {
    x + y
}

-> i32 表示这个函数返回一个 32 位整数。下面讲怎么”返回”。

13-4 Rust 特色:末行表达式即返回值

在 Rust 里,函数体最后一个表达式(不加分号)的值,自动成为返回值。上面 x + y 后面没有分号,它就是返回值。

对比用 return 关键字:

fn add(x: i32, y: i32) -> i32 {
    return x + y; // 也能用 return,但通常没必要
}

两种写法结果一样。但 Rust 习惯用”隐式返回”——去掉分号、让表达式当返回值,代码更简洁。

Tip

记忆口诀:想返回谁,就把它写成函数体最后一行、且别打分号。一旦你加了分号,它就从”表达式”变成”语句”,不再产生返回值,编译器会报”函数声明返回 i32 却返回了 ()“。

13-5 用 return 提前退出

return 关键字在需要”提前返回”时很有用,比如早退检查:

fn abs(n: i32) -> i32 {
    if n < 0 {
        return -n; // 负数直接取反返回
    }
    n // 非负数走这里,隐式返回
}

这里负数分支用 return 立刻结束函数;非负数分支落到最后一行 n 隐式返回。早期返回让逻辑更直白,不必套多层 if-else。

13-6 无返回值的函数

如果函数不返回有意义的值,有两种写法:

fn do_nothing() {
    // 没写 -> ,默认返回单元类型 ()
}

fn do_nothing2() -> () {
    // 显式写 () ,等价
}

不声明返回类型时,Rust 默认返回 ()(单元类型,前一章讲过)。所以”没有返回值”在 Rust 里其实也有个类型 (),保持了”一切皆类型”的一致。

13-7 函数的可组合性

函数可以调用函数,返回值能直接喂给别的函数:

fn square(n: i32) -> i32 {
    n * n
}

fn main() {
    let result = square(square(2)); // 先算 square(2)=4,再算 square(4)=16
    println!("结果是 {result}");
}

这种”小函数拼大逻辑”的写法,是函数式风格的常见做法。把复杂任务拆成多个单一职责的小函数,可读性和可测试性都更好。

Note

关于”语句”和”表达式”的区别,是 Rust 理解返回值的钥匙,也是新手最容易迷糊的概念。下一章专门掰开讲,它直接决定了你为什么能在函数末尾”不写 return 也能返回”。

13-8 该返回结果还是改外面的值

写函数时常纠结”该返回结果,还是通过参数改外面的值”。一般原则:函数基于输入算出东西,就返回它(纯函数风格),调用方自己决定怎么用;函数作用是”修改某个已有对象的状态”,就用 &mut self&mut 参数,不返回。前者更易于测试和推理,因为同样输入永远得到同样输出。

举个例子,算两数之和,写成 fn add(a: i32, b: i32) -> i32 { a + b } 返回结果,比写成 fn add(a: i32, b: i32, out: &mut i32) 把结果写进 out 参数清晰得多。Rust 里 out 参数那种 C 风格写法基本被返回值替代。

Note

当函数既要计算又需要报告”成功还是失败”,进阶部分你会学到用 Result 类型同时返回”结果或错误”,那是 Rust 处理错误的标准方式。现阶段先把”返回值”这一招用熟:能返回就返回,别滥用 mut 参数。

13-9 函数重载?Rust 没有

Rust 不支持函数重载——你不能有”同名但参数不同的两个函数”。如果硬写两个 add(i32)add(f64),编译器会报”重复定义”。这和 C++/Java 不同,初学者常踩。

// fn add(a: i32, b: i32) {}
// fn add(a: f64, b: f64) {} // 错!重复定义

替代方案:要么起不同的名字(如 add_i32add_f64),要么用后面学的泛型(fn add<T>(a: T, b: T))一次支持多种类型。入门阶段直接起不同名字最清楚,别期望”同名自动适配”。

Tip

没有重载有时反而清爽:你永远能靠名字唯一定位一个函数,不用去猜”这次调用匹配的是哪个版本”。配合编辑器跳转,阅读体验比重载语言更确定。

13-10 给函数写文档注释

给函数写文档注释,是专业习惯的起点。一个规范的公开函数,建议用 /// 说明”干什么、参数含义、返回值、给个可跑示例”。示例里的代码会被 cargo test 当测试跑(文档注释章会细讲这种 doctest 机制),所以文档永远和代码同步。

/// 计算两个整数的和。
///
/// # 参数
/// - `a`: 第一个加数
/// - `b`: 第二个加数
///
/// # 示例
/// ```
/// let r = add(2, 3);
/// assert_eq!(r, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
Tip

写练习时可以偷懒不写文档,但每当你写出一个”以后自己都会忘它是干嘛”的函数,顺手补一句 /// 说明,长期看省下的回头时间远超写注释的那几秒。

13-11 函数也是值:先打个招呼

在 Rust 里,函数本身也是一种”值”——它可以赋值给变量、能作为参数传给别的函数、也能被返回。这套能力属于进阶内容,现在你只要有个印象:因为函数是值,Rust 支持”把函数传来传去”的高阶写法,后面讲到闭包和迭代器时会正式展开。现阶段你先把”定义函数、传参、拿返回值”这套基础打牢,高阶玩法水到渠成。

Note

这种”函数能像数据一样被传递”的特性,是很多现代语言共有的设计。它让你可以写出”把某个操作交给另一个函数去执行”的通用代码,复用性很强。先知道有这回事,不急着现在用。

13-12 你可能会踩的坑

函数这块新手高频踩坑有几个,提前认清能省不少调试时间:

坑一:末行手滑加了分号。最常见的报错长这样:“expected i32, found ()”。原因几乎都是返回值那一行多写了一个分号,把表达式变成了语句、不再产出值。去掉末尾分号,让它保持”表达式”身份即可。

坑二:参数忘了标类型。写 fn f(x, y) 一定报”expected one of : or @, found ,”。Rust 不允许省略参数类型,老老实实写上 x: i32, y: i32。这是和 Python、TypeScript 很不一样的硬性规定。

坑三:以为有函数重载。同名但参数不同的两个函数 Rust 不允许存在,会报”重复定义”。需要适配多种类型时,要么起不同的名字(如 add_i32add_f64),要么等学到泛型一次支持多种类型。

坑四:把 return 当成唯一的返回方式。Rust 鼓励”末行表达式隐式返回”,return 主要用在需要提前退出的分支里。养成习惯,代码更地道、更短。

坑五:函数写在了被调用之后就以为不能用。Rust 不要求函数定义顺序,只要在同一个 crate 里、名字对得上,定义在后面也能被前面的代码调用。别被某些语言”必须先声明”的习惯带着走。

13-13 何时该把一段代码抽成函数

知道怎么写函数后,下一个问题是什么时候该写。一个朴素标准:同一段逻辑出现第二次,就该抽成函数。复制粘贴虽然快,但两处一旦要改、你只改了一处,bug 就来了。把这段逻辑命名、收进函数,调用点变成一行 do_thing(),可读性和可改性都更好。另一个信号:一段代码”读起来要绕一下才懂它在干嘛”——给它一个好名字的函数,等于顺手写了注释。

Tip

函数不是越短越好,也不是越长越好。目标是”每个函数做一件清晰的事、名字能说明它做啥”。如果函数名越来越长(如 计算税后并校验库存并更新订单),说明它干的事太多,该拆成几个小函数了。初学者常把整个流程写进 main 一个函数,试着把每一步抽出来,代码会清爽很多。

还有个误区:别为了”复用”过早抽象。如果一段逻辑只在一个地方用、且不太可能再用,先内联写清楚也行;等它真第二次出现再抽。过度设计(写一堆至今没用到的通用函数)反而增加理解成本。Rust 社区的整体取向是”务实”——需要时才抽象。

13-14 小结

fn 定义函数;参数必须标类型;返回值用 -> 类型 声明;末行不加引号的表达式自动返回,可用 return 提前退出。下一章我们深挖”语句 vs 表达式”,这是 Rust 最有特色、也最容易踩坑的基础概念之一。