Rust 函数与返回值详解:参数、表达式与返回类型

作者:程序员爱钓鱼日期:2026/7/14

《Rust 编程实战》系列第 9 篇

在前面的文章中,我们已经学习了变量、数据类型、常量和静态变量。

接下来,我们需要解决一个非常重要的问题:

如何把一段功能独立出来,并在程序中的多个地方重复使用?

答案就是:函数(Function)

函数是组织 Rust 程序最基本的方式之一。无论是命令行工具、Web 服务、桌面软件还是企业级项目,最终都会由大量函数共同组成。

本文将详细介绍:

  • 如何定义函数
  • 如何传递参数
  • 如何声明参数类型
  • 如何返回数据
  • Rust 中语句和表达式的区别
  • return 与隐式返回的区别
  • 单元类型 ()
  • 函数的常见错误和最佳实践

什么是函数?

函数可以理解为一段完成特定任务的代码。

例如:

1fn say_hello() {
2    println!("Hello, Rust!");
3}
4

这里定义了一个名为:

1say_hello
2

的函数。

它的功能是输出:

1Hello, Rust!
2

函数定义后不会自动执行,必须调用它。

完整代码如下:

1fn main() {
2    say_hello();
3}
4
5fn say_hello() {
6    println!("Hello, Rust!");
7}
8

运行结果:

1Hello, Rust!
2

fn 关键字

Rust 使用:

1fn
2

定义函数。

基本语法如下:

1fn 函数名() {
2    // 函数体
3}
4

例如:

1fn print_message() {
2    println!("正在学习 Rust");
3}
4

调用函数:

1print_message();
2

函数名通常使用蛇形命名法,也就是:

1snake_case
2

例如:

1fn get_user_name() {}
2
3fn calculate_total_price() {}
4
5fn save_order_data() {}
6

不推荐:

1fn GetUserName() {}
2

main 函数

每个 Rust 可执行程序都需要一个入口函数:

1fn main() {
2}
3

程序启动时,会首先执行 main 函数。

例如:

1fn main() {
2    println!("程序开始运行");
3}
4

对于普通可执行项目来说,main 函数是必须存在的。

不过,库项目通常使用:

1src/lib.rs
2

而不需要 main 函数。


函数可以定义在 main 后面吗?

可以。

例如:

1fn main() {
2    say_hello();
3}
4
5fn say_hello() {
6    println!("Hello");
7}
8

也可以写在 main 前面:

1fn say_hello() {
2    println!("Hello");
3}
4
5fn main() {
6    say_hello();
7}
8

Rust 不要求函数必须先定义再调用。

只要函数位于当前模块中,编译器就可以找到它。


函数参数

函数可以接收外部传入的数据,这些数据称为参数。

例如:

1fn greet(name: &str) {
2    println!("你好,{}!", name);
3}
4

调用:

1fn main() {
2    greet("Tom");
3    greet("Alice");
4}
5

输出:

1你好,Tom!
2你好,Alice!
3

这里:

1name: &str
2

表示函数接收一个名为 name 的参数,类型是 &str


Rust 函数参数必须指定类型

Rust 不允许省略函数参数类型。

错误写法:

1fn greet(name) {
2    println!("{}", name);
3}
4

编译器无法知道 name 的类型。

正确写法:

1fn greet(name: &str) {
2    println!("{}", name);
3}
4

这是 Rust 与部分动态语言不同的地方。

变量可以通过初始值推导类型:

1let age = 18;
2

但函数参数必须明确写出类型:

1fn show_age(age: i32) {
2    println!("{}", age);
3}
4

多个函数参数

函数可以接收多个参数。

例如:

1fn print_user(name: &str, age: u8) {
2    println!("姓名:{},年龄:{}", name, age);
3}
4

调用:

1fn main() {
2    print_user("Tom", 20);
3}
4

输出:

1姓名:Tom,年龄:20
2

每个参数都必须指定类型:

1fn print_user(name: &str, age: u8)
2

参数之间使用逗号分隔。


参数顺序必须一致

例如:

1fn print_user(name: &str, age: u8) {
2    println!("{} {}", name, age);
3}
4

正确调用:

1print_user("Alice", 18);
2

错误调用:

1print_user(18, "Alice");
2

因为参数位置和类型不匹配。

Rust 会在编译阶段报错,而不是等程序运行时才发现问题。


函数返回值

函数除了执行操作,也可以返回结果。

例如:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

这里:

1-> i32
2

表示函数返回一个 i32 类型的值。

调用:

1fn main() {
2    let result = add(10, 20);
3
4    println!("{}", result);
5}
6

输出:

130
2

返回值语法

Rust 函数返回值的语法如下:

1fn 函数名(参数) -> 返回类型 {
2    返回值
3}
4

例如:

1fn get_age() -> u8 {
2    18
3}
4

调用:

1fn main() {
2    let age = get_age();
3
4    println!("{}", age);
5}
6

输出:

118
2

为什么返回值后面没有分号?

这是 Rust 初学者最容易困惑的地方。

正确写法:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

错误写法:

1fn add(a: i32, b: i32) -> i32 {
2    a + b;
3}
4

区别就在于最后的分号。

在 Rust 中:

1a + b
2

是表达式,会产生一个值。

而:

1a + b;
2

加上分号后,就变成了语句,不再返回这个值。


语句与表达式

理解函数返回值,必须理解 Rust 中的两个概念:

  • 语句(Statement)
  • 表达式(Expression)

语句

语句执行操作,但通常不返回有意义的值。

例如:

1let x = 10;
2

这是一个语句。

再例如:

1println!("Hello");
2

也是一个语句。


表达式

表达式会计算并产生一个值。

例如:

110 + 20
2

结果是:

130
2

再例如:

1{
2    let x = 10;
3    x + 1
4}
5

整个代码块的值是:

111
2

代码块也可以返回值

Rust 中的大括号代码块本身也是表达式。

例如:

1fn main() {
2    let result = {
3        let a = 10;
4        let b = 20;
5
6        a + b
7    };
8
9    println!("{}", result);
10}
11

输出:

130
2

这里代码块最后一行:

1a + b
2

没有分号,因此这个值成为整个代码块的结果。


使用 return 返回值

Rust 也支持 return 关键字。

例如:

1fn add(a: i32, b: i32) -> i32 {
2    return a + b;
3}
4

注意:

使用 return 时,后面通常需要分号:

1return a + b;
2

这与隐式返回不同。

隐式返回:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

显式返回:

1fn add(a: i32, b: i32) -> i32 {
2    return a + b;
3}
4

两种写法结果相同。


什么时候使用 return?

如果返回值位于函数最后一行,通常推荐使用隐式返回:

1fn multiply(a: i32, b: i32) -> i32 {
2    a * b
3}
4

如果需要提前结束函数,则使用 return

例如:

1fn get_level(score: i32) -> &'static str {
2    if score < 0 {
3        return "分数无效";
4    }
5
6    if score >= 90 {
7        return "优秀";
8    }
9
10    if score >= 60 {
11        return "及格";
12    }
13
14    "不及格"
15}
16

这里多个分支都可能提前返回。


if 也可以作为表达式

Rust 中的 if 不只是控制语句,也可以返回值。

例如:

1fn get_status(age: u8) -> &'static str {
2    if age >= 18 {
3        "成年人"
4    } else {
5        "未成年人"
6    }
7}
8

调用:

1fn main() {
2    let status = get_status(20);
3
4    println!("{}", status);
5}
6

输出:

1成年人
2

还可以直接赋值:

1fn main() {
2    let age = 20;
3
4    let status = if age >= 18 {
5        "成年人"
6    } else {
7        "未成年人"
8    };
9
10    println!("{}", status);
11}
12

if 各分支类型必须一致

例如:

1let value = if true {
2    10
3} else {
4    20
5};
6

这是合法的,因为两个分支都是整数。

下面的代码会报错:

1let value = if true {
2    10
3} else {
4    "hello"
5};
6

因为一个分支返回整数,另一个分支返回字符串。

Rust 无法为变量确定统一类型。


没有返回值的函数

有些函数只执行操作,不需要返回数据。

例如:

1fn print_message() {
2    println!("Hello");
3}
4

这类函数实际上也有返回类型,只是返回的是:

1()
2

它称为:

单元类型(Unit Type)

上面的函数也可以写成:

1fn print_message() -> () {
2    println!("Hello");
3}
4

一般情况下不需要显式写出来。


单元类型 ()

() 表示没有实际数据的返回值。

例如:

1fn do_nothing() -> () {
2}
3

也可以:

1fn do_nothing() {
2}
3

两者等价。

单元类型在 Rust 中经常出现,例如:

  • 普通打印函数
  • 修改外部状态的函数
  • 没有返回数据的回调
  • Result<(), E> 中表示成功但不返回内容

例如:

1fn save_data() -> Result<(), String> {
2    println!("保存成功");
3
4    Ok(())
5}
6

这里:

1Ok(())
2

表示操作成功,但没有额外数据需要返回。


返回多个值

Rust 函数只能返回一个值,但可以借助元组返回多个数据。

例如:

1fn get_user() -> (&'static str, u8, bool) {
2    ("Tom", 20, true)
3}
4

调用:

1fn main() {
2    let user = get_user();
3
4    println!("姓名:{}", user.0);
5    println!("年龄:{}", user.1);
6    println!("状态:{}", user.2);
7}
8

也可以解构:

1fn main() {
2    let (name, age, active) = get_user();
3
4    println!("{} {} {}", name, age, active);
5}
6

实战:计算商品总价

下面写一个简单的函数,计算商品总价。

1fn calculate_total(price: f64, quantity: u32) -> f64 {
2    price * quantity as f64
3}
4
5fn main() {
6    let total = calculate_total(29.9, 3);
7
8    println!("商品总价:{:.2}", total);
9}
10

输出:

1商品总价:89.70
2

这里:

1quantity as f64
2

u32 转换为 f64,这样才能与价格相乘。


实战:计算折扣价格

1fn calculate_discount_price(price: f64, discount: f64) -> f64 {
2    if discount <= 0.0 || discount > 1.0 {
3        return price;
4    }
5
6    price * discount
7}
8
9fn main() {
10    let final_price = calculate_discount_price(100.0, 0.8);
11
12    println!("折后价格:{:.2}", final_price);
13}
14

输出:

1折后价格:80.00
2

当折扣值无效时,函数会提前返回原价。


实战:判断数字是否为偶数

1fn is_even(number: i32) -> bool {
2    number % 2 == 0
3}
4
5fn main() {
6    let result = is_even(10);
7
8    println!("是否为偶数:{}", result);
9}
10

输出:

1是否为偶数:true
2

这里:

1number % 2 == 0
2

本身就是一个布尔表达式,因此可以直接作为返回值。

不需要写成:

1if number % 2 == 0 {
2    true
3} else {
4    false
5}
6

前一种写法更简洁。


函数参数与变量可变性

函数参数默认也是不可变的。

例如:

1fn increase(number: i32) {
2    number += 1;
3}
4

会报错,因为 number 不可变。

可以改成:

1fn increase(mut number: i32) {
2    number += 1;
3
4    println!("{}", number);
5}
6

这里的 mut 只表示函数内部的参数变量可以修改。

完整示例:

1fn increase(mut number: i32) {
2    number += 1;
3
4    println!("函数内部:{}", number);
5}
6
7fn main() {
8    let number = 10;
9
10    increase(number);
11
12    println!("函数外部:{}", number);
13}
14

输出:

1函数内部:11
2函数外部:10
3

为什么外部还是 10?

因为整数类型默认会复制一份传入函数。函数内部修改的是参数副本,不是外部变量。

这个问题与 Rust 的所有权和 Copy 类型有关,后续会详细学习。


函数调用必须匹配返回类型

例如:

1fn get_age() -> i32 {
2    "18"
3}
4

会报错。

因为函数声明返回:

1i32
2

实际却返回:

1&str
2

正确写法:

1fn get_age() -> i32 {
2    18
3}
4

Rust 会在编译阶段检查返回值类型。


常见错误

参数没有指定类型

错误:

1fn add(a, b) {
2    println!("{}", a + b);
3}
4

正确:

1fn add(a: i32, b: i32) {
2    println!("{}", a + b);
3}
4

返回表达式后加了分号

错误:

1fn add(a: i32, b: i32) -> i32 {
2    a + b;
3}
4

正确:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

或者:

1fn add(a: i32, b: i32) -> i32 {
2    return a + b;
3}
4

返回值类型不一致

错误:

1fn get_value(ok: bool) -> i32 {
2    if ok {
3        10
4    } else {
5        "error"
6    }
7}
8

两个分支必须返回相同类型。

正确:

1fn get_value(ok: bool) -> i32 {
2    if ok {
3        10
4    } else {
5        0
6    }
7}
8

调用函数时参数数量不一致

函数定义:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

错误调用:

1add(10);
2

或者:

1add(10, 20, 30);
2

参数数量必须完全一致。


把打印结果当作返回值

例如:

1fn add(a: i32, b: i32) -> i32 {
2    println!("{}", a + b);
3}
4

println! 的返回值是 (),不是 i32

正确写法:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

打印应该放在调用位置:

1fn main() {
2    let result = add(10, 20);
3
4    println!("{}", result);
5}
6

函数设计最佳实践

一个函数只做一件事

不推荐把登录、保存数据、发送邮件和写日志全部放在一个函数里。

推荐拆分:

1fn validate_user() {}
2
3fn save_user() {}
4
5fn send_email() {}
6
7fn write_log() {}
8

这样代码更容易测试和维护。


函数名要表达意图

不推荐:

1fn do_it() {}
2

推荐:

1fn calculate_order_total() {}
2
3fn find_user_by_id() {}
4
5fn create_access_token() {}
6

看到函数名就能理解它的用途。


参数不要过多

如果一个函数有大量参数:

1fn create_user(
2    name: String,
3    age: u8,
4    email: String,
5    phone: String,
6    city: String,
7    address: String,
8) {
9}
10

后续可以考虑使用结构体:

1struct CreateUserRequest {
2    name: String,
3    age: u8,
4    email: String,
5    phone: String,
6    city: String,
7    address: String,
8}
9

然后函数只接收一个参数:

1fn create_user(request: CreateUserRequest) {
2}
3

优先返回结果,而不是在函数内部直接打印

不推荐:

1fn add(a: i32, b: i32) {
2    println!("{}", a + b);
3}
4

更推荐:

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4

这样调用方可以决定如何使用结果:

1let result = add(10, 20);
2
3println!("{}", result);
4

也可以继续参与计算:

1let total = add(10, 20) * 2;
2

完整练习

下面是一个简单的订单金额计算程序。

1fn calculate_subtotal(price: f64, quantity: u32) -> f64 {
2    price * quantity as f64
3}
4
5fn calculate_discount(subtotal: f64, discount_rate: f64) -> f64 {
6    if discount_rate <= 0.0 || discount_rate > 1.0 {
7        return subtotal;
8    }
9
10    subtotal * discount_rate
11}
12
13fn calculate_shipping(final_price: f64) -> f64 {
14    if final_price >= 99.0 {
15        0.0
16    } else {
17        10.0
18    }
19}
20
21fn main() {
22    let price = 39.9;
23    let quantity = 3;
24    let discount_rate = 0.9;
25
26    let subtotal = calculate_subtotal(price, quantity);
27    let discounted_price = calculate_discount(subtotal, discount_rate);
28    let shipping = calculate_shipping(discounted_price);
29    let total = discounted_price + shipping;
30
31    println!("商品小计:{:.2}", subtotal);
32    println!("折后金额:{:.2}", discounted_price);
33    println!("运费:{:.2}", shipping);
34    println!("订单总额:{:.2}", total);
35}
36

可能输出:

1商品小计:119.70
2折后金额:107.73
3运费:0.00
4订单总额:107.73
5

这个示例将不同功能拆分成多个函数:

  • calculate_subtotal:计算商品小计
  • calculate_discount:计算折扣金额
  • calculate_shipping:计算运费

这就是函数最重要的价值:拆分复杂逻辑,让代码更容易理解和复用。


本章小结

函数是 Rust 程序组织代码的基础。

通过本文,我们学习了:

  • 使用 fn 定义函数
  • 函数命名规范
  • 函数参数和参数类型
  • 使用 -> 声明返回类型
  • 隐式返回和 return
  • 语句与表达式的区别
  • 为什么返回表达式不能加分号
  • 单元类型 ()
  • 使用元组返回多个值
  • if 表达式返回值
  • 函数参数的可变性
  • 常见错误与函数设计实践

其中最重要的一点是:

Rust 函数体最后一个不带分号的表达式,会成为函数的返回值。

理解表达式和返回值之后,后面学习控制流、闭包、错误处理和所有权都会更加轻松。


下一篇预告

下一篇我们将学习 Rust 注释与文档注释,包括:

  • 普通单行注释
  • 多行注释
  • 文档注释 ///
  • 模块文档注释 //!
  • Markdown 文档
  • 文档代码示例
  • 使用 cargo doc 生成项目文档
  • 如何编写高质量的 Rust API 文档

Rust 函数与返回值详解:参数、表达式与返回类型》 是转载文章,点击查看原文


相关推荐


认识 Horizon UI · 15/17:用模板定制控制台
SkyWalking中文站2026/7/6

Horizon UI 系列第十五篇:整个控制台都由可编辑模板驱动。你可以把任意 layer 或 overview 打开成模板,在本地草稿里调整组件、widget 和文案,预览后发布到 OAP 给整个组织使用,并在发布前查看差异,也可以导出和导入。 译自英文原文:Meet Horizon UI · 15/17: Customization — Config-Driven Layer Templates。 这是 Meet Horizon UI 系列的第十五篇,也开启第五幕 make it yours


用视频数据采集 API 构建个人视频搜索引擎:从 C 罗频道到 Elasticsearch 全文检索
硬核科技工作室2026/6/28

一、视频元数据好看,但不好稳定拿 做视频搜索、内容监测或者训练数据准备时,第一步通常不是模型,也不是搜索算法,而是先拿到一批质量稳定的视频元数据。 比如我们想做一个个人视频搜索引擎,输入关键词 Cristiano,系统可以返回相关视频的标题、描述、播放量、时长、上传者和视频链接。听起来很简单,但真正做起来会发现,视频平台页面结构经常变化,不同入口返回的信息也不一样:频道页、搜索页、标签页、播放页,每个页面的数据组织方式都不同。 如果自己做这件事,通常会有几种方案。 第一种是自己写数据采集


AI 能写代码了,为什么我反而开始要求它先写文档?
Avan菜菜2026/6/19

最近在尝试用 AI 参与项目开发。 刚开始我的方式很简单: 提需求 ↓ 让 AI 直接实现 ↓ 不断返工 ↓ 继续补需求 结果非常熟悉: 功能能跑 代码越来越多 需求越来越乱 AI 上下文越来越长 后面谁都不敢接手 尤其是涉及: 前后端联动 权限体系 数据结构变更 API 契约 多阶段迭代 时,问题会迅速放大。 后来我接触到了 GitHub 开源的 Spec Kit。 它让我第一次把 AI 开发从: 直接写代码 变成: 先规格 ↓ 再设计 ↓ 再拆任务 ↓ 最后实现 整个过程开始变


企业智能助手的实践分享(LLM/RAG)
uzong2026/6/11

本文聚焦 AI 技术在企业级智能的实践,剖析项目实施过程中的关键挑战与避坑指南。 1. LLM 智能运维助手 1.1. 助手背景 在企业基础设施建设中,开放平台与基础服务承载着海量业务。随着系统复杂度的增加,日常运行中产生了庞大的日志告警数据。面对这些海量且繁杂的告警信息,传统的人工排查模式不仅耗时费力,且难以在“告警风暴”中迅速抽丝剥茧,成为制约研发效率的瓶颈。 希望助手能力致力于解决两大核心难题:一是应对海量日志告警的干扰,二是大幅缩短告警排查的平均耗时。 1.2. 案例效果 下面是一个案例


Linux shell脚本教程
诸神缄默不语2026/6/4

诸神缄默不语-个人技术博文与视频目录 Linux系统的命令行终端界面就是一个小黑窗,在里面敲命令执行任务。当你想执行一系列复杂的任务(比如连续执行多个命令、有逻辑判断规则等)时,光靠直接敲命令+回车就不够了,这时你就会将一系列任务的执行代码写到一个文本文件中,然后让Linux终端依次执行。这个文本文件就是shell脚本。 本文对Linux系统中的shell脚本进行简单介绍,包括其作用和基本写法。更高级的用法将在以后的教程中介绍。 对Linux系统的整体命令行操作教程,请参考我撰写的另一篇博文:


零基础webgis开发入门:HTML/CSS/JavaScript前端核心基础②
GIS6688002026/5/28

CSS:页面样式与布局美化 CSS 全称层叠样式表,核心作用是控制HTML元素的外观和布局,包括大小、颜色、背景、位置、边距等。 在WebGIS中,CSS直接决定地图的显示尺寸、是否全屏、页面是否有白边等关键效果。 CSS的核心逻辑可总结为两步走:选元素、改样式。 第一步:选元素(选择器) 1)什么是选择器: 选择器是CSS的核心,作用是从页面众多HTML元素中,筛选出需要修改样式的目标元素。 想象一群小黄人站你面前,你想把单眼的小黄人选出来变红色。 第一步:选出所


TOML 深度调研:对比 YAML、JSON 等五大配置格式,哪种最适合你的项目?
王若风2026/5/6

大家好,我是若风。 上周在配置一个 Rust 项目的时候,我盯着 Cargo.toml 发了一会儿呆。然后突然意识到一件事:我写了这么多年代码,跟配置文件打交道的时间可能比写业务逻辑还多。package.json、docker-compose.yml、tsconfig.json、.gitignore、terraform.tf……每个项目至少 3 到 5 个配置文件。 但说实话,我从来没认真想过一个问题:为什么这些工具要用不同的配置格式? YAML 写 Kubernetes 配置,JSON 写 p


如何将SVG格式文件转为PDF? 方便打印输出、正式汇报、跨平台展示
诸葛大钢铁2026/4/26

在日常设计、开发与文档交付过程中,SVG转PDF是一个非常高频但容易被忽视的需求。很多人一开始会觉得“只是格式转换而已”,但真正遇到输出打印、正式汇报或跨平台展示时才发现:SVG在不同设备上的兼容性并不总是稳定,而PDF才是更通用、更专业的交付格式。 尤其是在以下场景中,这个需求会变得非常明显: 设计稿需要提交评审或印刷 网页图标或流程图需要归档成标准文件 跨平台传输时避免样式错乱 因此,一个稳定、清晰、无损的SVG转PDF方案就显得非常重要。 一、设计软件直接导出


《swiftUI进阶 第9章SwiftUI 状态管理完全指南》
90后晨仔2026/4/18

概述 状态管理是 SwiftUI 应用的核心。本章将系统介绍从 iOS 13 到 iOS 17+ 的所有状态管理技术,包括传统的 ObservableObject 系列和现代的 @Observable 宏,帮助你根据项目需求选择最合适的方案。 第一部分:基础状态管理(iOS 13+) 1. @State:本地视图状态 @State 用于管理视图内部的简单状态,当值改变时自动刷新 UI。 struct CounterView: View { @State private var coun


Nginx 从入门到精通:全面解析与实战指南
程序员果子2026/4/10

目录 前言:为什么要学 Nginx? 一、Nginx 基础入门:从零搭建第一个服务 1.1 初识 Nginx:它是什么,能做什么? 1.2 第一个 Nginx 服务:最小化配置实战 1.3 安装:Linux 里的 Nginx 魔法:从下载到部署,轻松拿捏! 二、核心架构与配置解析:读懂 Nginx 的 "运行逻辑" 2.1 架构精髓:Master-Worker 进程模型 2.2 配置骨架:从 http 到 location 的层级关系 2.3 静态资源服务:Nginx 的 "原

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读