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

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

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

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

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

答案就是:函数(Function)

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

本文将详细介绍:

  • 如何定义函数

  • 如何传递参数

  • 如何声明参数类型

  • 如何返回数据

  • Rust 中语句和表达式的区别

  • return 与隐式返回的区别

  • 单元类型 ()

  • 函数的常见错误和最佳实践


什么是函数?

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

例如:

rust 复制代码
fn say_hello() {
    println!("Hello, Rust!");
}

这里定义了一个名为:

text 复制代码
say_hello

的函数。

它的功能是输出:

text 复制代码
Hello, Rust!

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

完整代码如下:

rust 复制代码
fn main() {
    say_hello();
}

fn say_hello() {
    println!("Hello, Rust!");
}

运行结果:

text 复制代码
Hello, Rust!

fn 关键字

Rust 使用:

rust 复制代码
fn

定义函数。

基本语法如下:

rust 复制代码
fn 函数名() {
    // 函数体
}

例如:

rust 复制代码
fn print_message() {
    println!("正在学习 Rust");
}

调用函数:

rust 复制代码
print_message();

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

text 复制代码
snake_case

例如:

rust 复制代码
fn get_user_name() {}

fn calculate_total_price() {}

fn save_order_data() {}

不推荐:

rust 复制代码
fn GetUserName() {}

main 函数

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

rust 复制代码
fn main() {
}

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

例如:

rust 复制代码
fn main() {
    println!("程序开始运行");
}

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

不过,库项目通常使用:

text 复制代码
src/lib.rs

而不需要 main 函数。


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

可以。

例如:

rust 复制代码
fn main() {
    say_hello();
}

fn say_hello() {
    println!("Hello");
}

也可以写在 main 前面:

rust 复制代码
fn say_hello() {
    println!("Hello");
}

fn main() {
    say_hello();
}

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

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


函数参数

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

例如:

rust 复制代码
fn greet(name: &str) {
    println!("你好,{}!", name);
}

调用:

rust 复制代码
fn main() {
    greet("Tom");
    greet("Alice");
}

输出:

text 复制代码
你好,Tom!
你好,Alice!

这里:

rust 复制代码
name: &str

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


Rust 函数参数必须指定类型

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

错误写法:

rust 复制代码
fn greet(name) {
    println!("{}", name);
}

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

正确写法:

rust 复制代码
fn greet(name: &str) {
    println!("{}", name);
}

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

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

rust 复制代码
let age = 18;

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

rust 复制代码
fn show_age(age: i32) {
    println!("{}", age);
}

多个函数参数

函数可以接收多个参数。

例如:

rust 复制代码
fn print_user(name: &str, age: u8) {
    println!("姓名:{},年龄:{}", name, age);
}

调用:

rust 复制代码
fn main() {
    print_user("Tom", 20);
}

输出:

text 复制代码
姓名:Tom,年龄:20

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

rust 复制代码
fn print_user(name: &str, age: u8)

参数之间使用逗号分隔。


参数顺序必须一致

例如:

rust 复制代码
fn print_user(name: &str, age: u8) {
    println!("{} {}", name, age);
}

正确调用:

rust 复制代码
print_user("Alice", 18);

错误调用:

rust 复制代码
print_user(18, "Alice");

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

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


函数返回值

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

例如:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

这里:

rust 复制代码
-> i32

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

调用:

rust 复制代码
fn main() {
    let result = add(10, 20);

    println!("{}", result);
}

输出:

text 复制代码
30

返回值语法

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

rust 复制代码
fn 函数名(参数) -> 返回类型 {
    返回值
}

例如:

rust 复制代码
fn get_age() -> u8 {
    18
}

调用:

rust 复制代码
fn main() {
    let age = get_age();

    println!("{}", age);
}

输出:

text 复制代码
18

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

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

正确写法:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

错误写法:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b;
}

区别就在于最后的分号。

在 Rust 中:

rust 复制代码
a + b

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

而:

rust 复制代码
a + b;

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


语句与表达式

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

  • 语句(Statement)

  • 表达式(Expression)

语句

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

例如:

rust 复制代码
let x = 10;

这是一个语句。

再例如:

rust 复制代码
println!("Hello");

也是一个语句。


表达式

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

例如:

rust 复制代码
10 + 20

结果是:

text 复制代码
30

再例如:

rust 复制代码
{
    let x = 10;
    x + 1
}

整个代码块的值是:

text 复制代码
11

代码块也可以返回值

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

例如:

rust 复制代码
fn main() {
    let result = {
        let a = 10;
        let b = 20;

        a + b
    };

    println!("{}", result);
}

输出:

text 复制代码
30

这里代码块最后一行:

rust 复制代码
a + b

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


使用 return 返回值

Rust 也支持 return 关键字。

例如:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

注意:

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

rust 复制代码
return a + b;

这与隐式返回不同。

隐式返回:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

显式返回:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

两种写法结果相同。


什么时候使用 return?

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

rust 复制代码
fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

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

例如:

rust 复制代码
fn get_level(score: i32) -> &'static str {
    if score < 0 {
        return "分数无效";
    }

    if score >= 90 {
        return "优秀";
    }

    if score >= 60 {
        return "及格";
    }

    "不及格"
}

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


if 也可以作为表达式

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

例如:

rust 复制代码
fn get_status(age: u8) -> &'static str {
    if age >= 18 {
        "成年人"
    } else {
        "未成年人"
    }
}

调用:

rust 复制代码
fn main() {
    let status = get_status(20);

    println!("{}", status);
}

输出:

text 复制代码
成年人

还可以直接赋值:

rust 复制代码
fn main() {
    let age = 20;

    let status = if age >= 18 {
        "成年人"
    } else {
        "未成年人"
    };

    println!("{}", status);
}

if 各分支类型必须一致

例如:

rust 复制代码
let value = if true {
    10
} else {
    20
};

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

下面的代码会报错:

rust 复制代码
let value = if true {
    10
} else {
    "hello"
};

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

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


没有返回值的函数

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

例如:

rust 复制代码
fn print_message() {
    println!("Hello");
}

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

rust 复制代码
()

它称为:

单元类型(Unit Type)

上面的函数也可以写成:

rust 复制代码
fn print_message() -> () {
    println!("Hello");
}

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


单元类型 ()

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

例如:

rust 复制代码
fn do_nothing() -> () {
}

也可以:

rust 复制代码
fn do_nothing() {
}

两者等价。

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

  • 普通打印函数

  • 修改外部状态的函数

  • 没有返回数据的回调

  • Result<(), E> 中表示成功但不返回内容

例如:

rust 复制代码
fn save_data() -> Result<(), String> {
    println!("保存成功");

    Ok(())
}

这里:

rust 复制代码
Ok(())

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


返回多个值

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

例如:

rust 复制代码
fn get_user() -> (&'static str, u8, bool) {
    ("Tom", 20, true)
}

调用:

rust 复制代码
fn main() {
    let user = get_user();

    println!("姓名:{}", user.0);
    println!("年龄:{}", user.1);
    println!("状态:{}", user.2);
}

也可以解构:

rust 复制代码
fn main() {
    let (name, age, active) = get_user();

    println!("{} {} {}", name, age, active);
}

实战:计算商品总价

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

rust 复制代码
fn calculate_total(price: f64, quantity: u32) -> f64 {
    price * quantity as f64
}

fn main() {
    let total = calculate_total(29.9, 3);

    println!("商品总价:{:.2}", total);
}

输出:

text 复制代码
商品总价:89.70

这里:

rust 复制代码
quantity as f64

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


实战:计算折扣价格

rust 复制代码
fn calculate_discount_price(price: f64, discount: f64) -> f64 {
    if discount <= 0.0 || discount > 1.0 {
        return price;
    }

    price * discount
}

fn main() {
    let final_price = calculate_discount_price(100.0, 0.8);

    println!("折后价格:{:.2}", final_price);
}

输出:

text 复制代码
折后价格:80.00

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


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

rust 复制代码
fn is_even(number: i32) -> bool {
    number % 2 == 0
}

fn main() {
    let result = is_even(10);

    println!("是否为偶数:{}", result);
}

输出:

text 复制代码
是否为偶数:true

这里:

rust 复制代码
number % 2 == 0

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

不需要写成:

rust 复制代码
if number % 2 == 0 {
    true
} else {
    false
}

前一种写法更简洁。


函数参数与变量可变性

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

例如:

rust 复制代码
fn increase(number: i32) {
    number += 1;
}

会报错,因为 number 不可变。

可以改成:

rust 复制代码
fn increase(mut number: i32) {
    number += 1;

    println!("{}", number);
}

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

完整示例:

rust 复制代码
fn increase(mut number: i32) {
    number += 1;

    println!("函数内部:{}", number);
}

fn main() {
    let number = 10;

    increase(number);

    println!("函数外部:{}", number);
}

输出:

text 复制代码
函数内部:11
函数外部:10

为什么外部还是 10?

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

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


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

例如:

rust 复制代码
fn get_age() -> i32 {
    "18"
}

会报错。

因为函数声明返回:

rust 复制代码
i32

实际却返回:

rust 复制代码
&str

正确写法:

rust 复制代码
fn get_age() -> i32 {
    18
}

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


常见错误

参数没有指定类型

错误:

rust 复制代码
fn add(a, b) {
    println!("{}", a + b);
}

正确:

rust 复制代码
fn add(a: i32, b: i32) {
    println!("{}", a + b);
}

返回表达式后加了分号

错误:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b;
}

正确:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

或者:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

返回值类型不一致

错误:

rust 复制代码
fn get_value(ok: bool) -> i32 {
    if ok {
        10
    } else {
        "error"
    }
}

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

正确:

rust 复制代码
fn get_value(ok: bool) -> i32 {
    if ok {
        10
    } else {
        0
    }
}

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

函数定义:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

错误调用:

rust 复制代码
add(10);

或者:

rust 复制代码
add(10, 20, 30);

参数数量必须完全一致。


把打印结果当作返回值

例如:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    println!("{}", a + b);
}

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

正确写法:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

打印应该放在调用位置:

rust 复制代码
fn main() {
    let result = add(10, 20);

    println!("{}", result);
}

函数设计最佳实践

一个函数只做一件事

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

推荐拆分:

rust 复制代码
fn validate_user() {}

fn save_user() {}

fn send_email() {}

fn write_log() {}

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


函数名要表达意图

不推荐:

rust 复制代码
fn do_it() {}

推荐:

rust 复制代码
fn calculate_order_total() {}

fn find_user_by_id() {}

fn create_access_token() {}

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


参数不要过多

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

rust 复制代码
fn create_user(
    name: String,
    age: u8,
    email: String,
    phone: String,
    city: String,
    address: String,
) {
}

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

rust 复制代码
struct CreateUserRequest {
    name: String,
    age: u8,
    email: String,
    phone: String,
    city: String,
    address: String,
}

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

rust 复制代码
fn create_user(request: CreateUserRequest) {
}

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

不推荐:

rust 复制代码
fn add(a: i32, b: i32) {
    println!("{}", a + b);
}

更推荐:

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

rust 复制代码
let result = add(10, 20);

println!("{}", result);

也可以继续参与计算:

rust 复制代码
let total = add(10, 20) * 2;

完整练习

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

rust 复制代码
fn calculate_subtotal(price: f64, quantity: u32) -> f64 {
    price * quantity as f64
}

fn calculate_discount(subtotal: f64, discount_rate: f64) -> f64 {
    if discount_rate <= 0.0 || discount_rate > 1.0 {
        return subtotal;
    }

    subtotal * discount_rate
}

fn calculate_shipping(final_price: f64) -> f64 {
    if final_price >= 99.0 {
        0.0
    } else {
        10.0
    }
}

fn main() {
    let price = 39.9;
    let quantity = 3;
    let discount_rate = 0.9;

    let subtotal = calculate_subtotal(price, quantity);
    let discounted_price = calculate_discount(subtotal, discount_rate);
    let shipping = calculate_shipping(discounted_price);
    let total = discounted_price + shipping;

    println!("商品小计:{:.2}", subtotal);
    println!("折后金额:{:.2}", discounted_price);
    println!("运费:{:.2}", shipping);
    println!("订单总额:{:.2}", total);
}

可能输出:

text 复制代码
商品小计:119.70
折后金额:107.73
运费:0.00
订单总额:107.73

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

  • calculate_subtotal:计算商品小计

  • calculate_discount:计算折扣金额

  • calculate_shipping:计算运费

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


本章小结

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

通过本文,我们学习了:

  • 使用 fn 定义函数

  • 函数命名规范

  • 函数参数和参数类型

  • 使用 -> 声明返回类型

  • 隐式返回和 return

  • 语句与表达式的区别

  • 为什么返回表达式不能加分号

  • 单元类型 ()

  • 使用元组返回多个值

  • if 表达式返回值

  • 函数参数的可变性

  • 常见错误与函数设计实践

其中最重要的一点是:

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

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


下一篇预告

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

  • 普通单行注释

  • 多行注释

  • 文档注释 ///

  • 模块文档注释 //!

  • Markdown 文档

  • 文档代码示例

  • 使用 cargo doc 生成项目文档

  • 如何编写高质量的 Rust API 文档

相关推荐
花褪残红青杏小8 小时前
Rust图像处理第17节-Julia 朱利亚分形:拖动常数 c 看图实时变化
rust·webassembly·图形学
遨游DATA12 小时前
Spring Boot 启动失败排查:如何区分多 Bean 冲突与清理阶段异常
java·spring boot·后端
IT_陈寒12 小时前
Java线程池这个坑我算是踩明白了
前端·人工智能·后端
An_s12 小时前
rust开发wasm与浏览器(js)交互
javascript·rust·wasm
掉头发的王富贵12 小时前
我来入职新公司两个月了,为什么只写了100行代码?
后端·ai编程
从零开始的代码生活_13 小时前
C++ 内存管理:从内存分区到 new/delete 底层原理
开发语言·c++·后端
315356691313 小时前
Superpowers:GPT-5.6 时代下的流程毒瘤
前端·后端
Mr -老鬼14 小时前
Zed WASM扩展开发全套踩坑记录(WASI适配、LSP插件开发避坑指南)
rust·wasm·lsp·zed·ai ide·语言服务器
神奇小汤圆15 小时前
2026 Java岗面试题库(已收录GitHub),覆盖所有考点
后端