Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5. 函数

5.1 简介

  在日常生活中,面对复杂任务,人们通常会将其拆分为若干个简单任务,化繁为简逐步完成目标。程序设计也遵循这一思想:将复杂功能拆分为多个函数,分别负责不同的任务,而不是将所有逻辑集中在 main 函数中。这样不仅可以使程序结构更加清晰,还能复用已有函数,提升代码的可读性和可维护性。

5.2 函数声明

  函数的定义语法形式为 fn 函数名(参数列表) -> 返回类型 { ... }。其中,fn 是函数声明关键字,函数名用于标识函数本身,参数列表用于定义函数接收的输入数据及其类型,返回类型用于指定函数输出的数据类型。参数列表和返回类型均为可选项,函数可以根据需求选择是否接收参数以及是否返回结果。

// 1. 无参数、无返回值的函数
fn say_hello() {
    println!("Hello, Rust!");
}

// 2. 带参数的函数,参数可以有多个,多个参数之间以逗号分割,函数中可以像变量一样使用函数参数
fn print_sum(a: i32, b: i32) {
    println!("sum = {}", a + b);
}

// 3. 带参数和返回值的函数
fn add(a: i32, b: i32) -> i32 {
    return a + b;          // 使用 return 关键字返回计算结果
}

fn main() {
    say_hello();
    print_sum(3, 5);
    let result = add(10, 20);
    println!("result = {}", result);
}
shell> cargo run
Hello, Rust!
sum = 8
result = 30

5.3 函数返回值

  在 Rust 中,如果函数没有显式指定返回类型,则默认返回单元类型 ()。函数可以使用 return 关键字显式返回值,或省略 return 关键字,直接将函数体最后一个表达式的值作为返回值。

use std::any::type_name_of_val;

fn foo() {
    // 没有显式指定返回值,默认返回单元类型 ()
}

fn add(a: i32, b: i32) -> i32 {
    // 使用 return 关键字显式指定返回值
    return a + b;
}

fn multiply(a: i32, b: i32) -> i32 {
    // 省略 `return` 关键字,将函数体最后一个表达式的值作为返回值
    a * b
}

fn main() {
    let f = foo();
    println!("{}", type_name_of_val(&f));   // 输出:()
    
    let sum = add(10, 20);                  // 输出:30
    println!("sum = {}", sum);
    
    let product = multiply(10, 20);         // 输出:200
    println!("product = {}", product);
}
shell> cargo run
()
sum = 30
product = 200

5.4 函数并联数据类型

  函数可与数据类型关联,将数据及其相关行为组织在一起,从而提升代码的可读性与可维护性。在 Rust 中,可以通过 impl 数据类型 块为类型定义关联函数,仅与数据类型本身关联、不依赖具体实例数据的函数称为静态函数;需要通过具体实例访问数据的函数称为实例函数。在下述示例中,为表示矩形的结构体定义了一个静态函数和三个实例函数。

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 静态函数:通过类型名称奶即可调用,不依赖实例
    fn describe() {
        println!("Rectangle 类型用于表示一个矩形,支持计算面积和周长。");
    }

    // 实例函数:&self 表示不可变引用,函数只能读取数据,不能修改
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 实例函数:&mut self 表示可变引用,函数可以修改实例数据
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }

    // 实例函数:self 表示获取所有权,调用后实例无法继续使用
    fn perimeter(self) -> u32 {
        2 * (self.width + self.height)
    }
}

fn main() {
    // 静态函数:通过类型名调用,不需要实例,调用格式为 类型名::函数名
    Rectangle::describe();

    // 创建实例并初始化数据
    let mut rect = Rectangle { width: 10, height: 20 };

    // 调用不可变引用的实例函数:只读取数据
    let area = rect.area();
    println!("面积: {area}");

    // 调用可变引用的实例函数:修改实例的数据
    rect.set_width(15);
    println!("修改后宽度: {}", rect.width);

    // 调用获取所有权的函数,调用后实例不可再使用
    let perimeter = rect.perimeter();
    println!("周长: {perimeter}");

    // 下面的代码会编译报错,因为 `rect` 的所有权已被转移
    // println!("{}", rect.width); // 错误:实例的所有权已被转移
}
shell> cargo run
Rectangle 类型用于表示一个矩形,支持计算面积和周长。
面积: 200
修改后宽度: 15
周长: 70

  除结构体外,枚举类型同样可定义关联函数,将其数据表示与相关操作组织在一起,使得枚举不仅可以描述各变体的状态,还能提供处理这些状态的函数。

// 定义枚举
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

impl Shape {
    // 针对不同的枚举变体,采用不同的面积计算方式
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => 3.14 * r * r,
            Shape::Rectangle(w, h) => w * h,
        }
    }
}

fn main() {

    // 创建枚举实例
    let rectangle = Shape::Rectangle(10.0, 5.0);
    println!("矩形面积: {}", rectangle.area());

    let circle = Shape::Circle(10.0);
    println!("圆面积: {}", circle.area());
}
shell> cargo run
矩形面积: 50
圆面积: 314