Rust 的流程控制与函数

流程控制和函数是任何编程语言的核心组成部分,Rust 也不例外。

本章介绍了 Rust 中的流程控制与函数相关知识,包括:

  • 条件判断:if-else结构和if let模式匹配
  • 循环结构:loop(无限循环)、while(条件循环)和for(迭代循环)
  • 函数:定义、参数、返回值和函数指针的使用
  • 函数高级特性:默认参数的模拟、可变参数的实现和递归函数
  • 代码块与作用域:作用域规则和变量遮蔽特性

一、条件判断(if-else、if let)

Rust 的条件判断用于根据不同条件执行不同代码分支,主要包括if-else结构和if let模式匹配。

1.1 if-else 基本用法

Rust 的if条件不需要用括号()包裹,但其代码块必须用{}包围。条件表达式的结果必须是bool类型(Rust 不会自动进行类型转换)。

fn main() {
    let age = 18;
    
    if age >= 18 {
        println!("已成年");
    } else if age >= 13 {
        println!("青少年");
    } else {
        println!("儿童");
    }
    
    // 错误示例:条件必须是bool类型
    // let num = 5;
    // if num {  // 编译错误:expected `bool`, found `i32`
    //     println!("num is non-zero");
    // }
}

if表达式可以作为赋值语句的一部分,因为 Rust 中一切都是表达式(有返回值):

fn main() {
    let score = 85;
    let grade = if score >= 90 {
        'A'
    } else if score >= 80 {
        'B'
    } else {
        'C'
    };
    println!("成绩等级: {}", grade);  // 输出:成绩等级: B
}

1.2 if let 模式匹配

if let是一种简化的模式匹配,适用于只关心一种匹配情况的场景,比match表达式更简洁。常用于处理Option类型或枚举。

fn main() {
    // 处理Option类型
    let some_value: Option<i32> = Some(5);
    
    // 传统match方式
    match some_value {
        Some(x) => println!("找到值: {}", x),
        None => (),  // 不关心None的情况,只做占位
    }
    
    // 等价的if let方式
    if let Some(x) = some_value {
        println!("找到值: {}", x);  // 输出:找到值: 5
    }
    
    // 结合else处理不匹配的情况
    let another_value: Option<i32> = None;
    if let Some(x) = another_value {
        println!("找到值: {}", x);
    } else {
        println!("没有值");  // 输出:没有值
    }
    
    // 处理枚举
    enum Fruit {
        Apple,
        Banana(String),
    }
    
    let fruit = Fruit::Banana("黄香蕉".to_string());
    if let Fruit::Banana(color) = fruit {
        println!("这是{}的香蕉", color);  // 输出:这是黄香蕉的香蕉
    }
}

二、循环结构(loop、while、for 与迭代器)

Rust 提供了三种循环结构:loop(无限循环)、while(条件循环)和for(迭代循环),其中for循环与迭代器结合使用最为常见。

2.1 loop 无限循环

loop创建一个无限循环,必须使用break才能退出。它的特殊之处是可以返回一个值。

fn main() {
    // 基本用法
    let mut count = 0;
    loop {
        count += 1;
        if count == 3 {
            println!("计数到3,退出循环");  // 输出:计数到3,退出循环
            break;  // 退出循环
        }
    }
    
    // 带返回值的loop
    let mut sum = 0;
    let result = loop {
        sum += 5;
        if sum >= 20 {
            break sum;  // 返回sum的值
        }
    };
    println!("循环返回值: {}", result);  // 输出:循环返回值: 20
}

2.2 while 条件循环

while循环在条件为true时持续执行,适合不确定循环次数的场景。

fn main() {
    let mut number = 3;
    
    while number > 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("发射!");
    // 输出:
    // 3!
    // 2!
    // 1!
    // 发射!
}

2.3 for 循环与迭代器

for循环是 Rust 中最常用的循环方式,通常与迭代器配合使用,用于遍历集合或范围。

fn main() {
    // 遍历范围(左闭右开)
    for i in 1..5 {  // 1到4(不包含5)
        println!("{}", i);  // 输出:1 2 3 4
    }
    
    // 遍历数组
    let fruits = ["苹果", "香蕉", "橙子"];
    for fruit in fruits {
        println!("水果: {}", fruit);
    }
    
    // 使用迭代器方法enumerate获取索引和值
    for (index, value) in fruits.iter().enumerate() {
        println!("索引{}: {}", index, value);
        // 输出:
        // 索引0: 苹果
        // 索引1: 香蕉
        // 索引2: 橙子
    }
    
    // 遍历集合(需要引入标准库)
    use std::collections::VecDeque;
    let mut queue = VecDeque::new();
    queue.push_back("a");
    queue.push_back("b");
    
    for item in queue {
        println!("队列元素: {}", item);
    }
}

Rust 的迭代器提供了丰富的方法(如mapfiltercollect等),可以进行复杂的数据处理:

fn main() {
    let numbers = 1..10;
    
    // 过滤偶数并乘以2
    let result: Vec<i32> = numbers
        .filter(|x| x % 2 == 0)
        .map(|x| x * 2)
        .collect();
    
    println!("处理结果: {:?}", result);  // 输出:处理结果: [4, 8, 12, 16]
}

三、函数定义与调用(参数、返回值、函数指针)

函数是代码复用和逻辑组织的基本单元,Rust 中使用fn关键字定义函数。

3.1 函数定义与调用

Rust 函数的基本结构:fn 函数名(参数列表) -> 返回值类型 { 函数体 }

// 定义一个简单函数
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// 带返回值的函数
fn add(a: i32, b: i32) -> i32 {
    a + b  // 表达式末尾没有分号,作为返回值
}

// 多个返回值(通过元组实现)
fn split_name(full_name: &str) -> (&str, &str) {
    let parts: Vec<&str> = full_name.split_whitespace().collect();
    (parts[0], parts[1])  // 返回元组
}

fn main() {
    // 调用函数
    greet("Rust");  // 输出:Hello, Rust!
    
    let sum = add(3, 5);
    println!("3 + 5 = {}", sum);  // 输出:3 + 5 = 8
    
    let (first, last) = split_name("John Doe");
    println!("名: {}, 姓: {}", first, last);  // 输出:名: John, 姓: Doe
}

注意:

函数参数必须指定类型
函数返回值通过->指定类型
函数体中最后一个表达式(不带分号)作为返回值,也可以使用return关键字提前返回

3.2 函数参数的可变性

Rust 函数参数默认是不可变的,若要在函数内修改参数,需要显式声明mut

fn main() {
    let mut x = 5;
    modify_value(&mut x);  // 传递可变引用
    println!("修改后的值: {}", x);  // 输出:修改后的值: 10
}

// 接收可变引用参数
fn modify_value(num: &mut i32) {
    *num *= 2;  // 使用*解引用
}

3.3 函数指针

函数指针(function pointer)允许将函数作为参数传递或存储在变量中,类型表示为fn(参数类型) -> 返回值类型

// 定义几个数学函数
fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

// 接收函数指针作为参数
fn calculate(operation: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
    operation(x, y)
}

fn main() {
    let a = 10;
    let b = 5;
    
    // 传递函数作为参数
    let sum = calculate(add, a, b);
    let diff = calculate(subtract, a, b);
    
    println!("{} + {} = {}", a, b, sum);  // 输出:10 + 5 = 15
    println!("{} - {} = {}", a, b, diff);  // 输出:10 - 5 = 5
    
    // 存储函数指针
    let op: fn(i32, i32) -> i32 = add;
    println!("使用函数指针: {}", op(3, 4));  // 输出:7
}

四、函数的高级特性(默认参数、可变参数、递归)

Rust 的函数系统提供了一些高级特性,虽然有些特性的实现方式与其他语言不同,但能满足复杂的编程需求。

4.1 默认参数

Rust没有内置的默认参数语法,但可以通过以下方式模拟:

使用结构体 + 默认实现

// 定义配置结构体
#[derive(Default)]
struct Config {
    timeout: u32,
    retries: u8,
}

// 接收配置参数的函数
fn connect(config: Config) {
    println!("连接配置 - 超时: {}ms, 重试次数: {}", config.timeout, config.retries);
}

fn main() {
    // 使用默认配置
    connect(Config::default());  // 输出:连接配置 - 超时: 0ms, 重试次数: 0
    
    // 自定义部分配置
    connect(Config {
        timeout: 5000,
        ..Config::default()  // 其余使用默认值
    });  // 输出:连接配置 - 超时: 5000ms, 重试次数: 0
}

使用函数重载

// 不使用 derive(Default),而是手动实现
struct Config {
    timeout: u32,
    retries: u8,
}

// 手动实现 Default trait,定义自定义默认值
impl Default for Config {
    fn default() -> Self {
        Config {
            timeout: 5000,  // 自定义默认超时为 5000ms
            retries: 3,     // 自定义默认重试次数为 3 次
        }
    }
}


// 接收配置参数的函数
fn connect(config: Config) {
    println!("连接配置 - 超时: {}ms, 重试次数: {}", config.timeout, config.retries);
}

fn main() {
    // 使用默认配置
    connect(Config::default());  // 输出:连接配置 - 超时: 0ms, 重试次数: 0
    
    // 自定义部分配置
    connect(Config {
        timeout: 1000,
        ..Config::default()  // 其余使用默认值
    });  // 输出:连接配置 - 超时: 5000ms, 重试次数: 0
}

4.2 定义接口

在 Rust 中,trait(特质)是一种定义方法集合的机制,类似于其他语言中的 “接口(interface)”,但功能更灵活。它主要用于:

  1. 定义一组行为(方法)的规范,要求实现者必须提供这些行为的具体逻辑;
  2. 实现代码复用和多态(同一接口的不同实现)。

通俗理解:trait 是 “行为规范”

可以把 trait 想象成一份 “协议”,它规定了 “实现者必须具备哪些能力(方法)”。例如,Greet trait 定义了 “打招呼” 的能力,任何类型(结构体、枚举等)只要只要实现 Greet,就必须提供 greet 方法的具体实现。

基本用法示例

// 定义一个 trait(行为规范):要求实现者必须有 greet 方法
trait Greet {
    // 声明方法(只定义签名,不写实现)
    fn greet(&self);
}

// 定义一个结构体(实现者1)
struct Person {
    name: String,
}

// 为 Person 实现 Greet trait(遵守规范)
impl Greet for Person {
    fn greet(&self) {
        println!("Hello, I'm {}", self.name);
    }
}

// 定义另一个结构体(实现者2)
struct Robot {
    model: String,
}

// 为 Robot 实现 Greet trait(不同实现)
impl Greet for Robot {
    fn greet(&self) {
        println!("Greetings, human. I am {}", self.model);
    }
}

fn main() {
    let alice = Person { name: "Alice".to_string() };
    let r2d2 = Robot { model: "R2-D2".to_string() };

    // 调用各自的 greet 方法(多态:同一方法名,不同实现)
    alice.greet();  // 输出:Hello, I'm Alice
    r2d2.greet();   // 输出:Greetings, human. I am R2-D2
}

trait 的核心特性

1.强制实现

任何类型要使用 trait,必须实现其所有方法(除非方法有默认实现)。

2.默认方法

可以在 trait 中直接提供方法的默认实现,实现者可以选择覆盖或直接使用:

trait Greet {
    // 带默认实现的方法
    fn greet(&self) {
        println!("Hello!");
    }
}

struct Cat;
struct Dog;
// 不覆盖默认方法,直接使用
impl Greet for Cat {}
// 覆盖默认方法
impl Greet for Dog {
    fn greet(&self) {
        println!("Hello! Dog!");
    }
}
fn main() {
    Cat.greet();  // 输出:Hello!
    Dog.greet();  // 输出:Hello! Dog!
}
3. 规范参数 / 返回值

可以用 trait 约束函数的参数或返回值,实现 “泛型多态”:

// 定义一个 trait(行为规范):要求实现者必须有 greet 方法
trait Greet {
    // 声明方法(只定义签名,不写实现)
    fn greet(&self);
}

// 定义一个结构体(实现者1)
struct Person {
    name: String,
}

// 为 Person 实现 Greet trait(遵守规范)
impl Greet for Person {
    fn greet(&self) {
        println!("Hello, I'm {}", self.name);
    }
}

// 定义另一个结构体(实现者2)
struct Robot {
    model: String,
}

// 为 Robot 实现 Greet trait(不同实现)
impl Greet for Robot {
    fn greet(&self) {
        println!("Greetings, human. I am {}", self.model);
    }
}

// 接受任何实现了 Greet 的类型
fn say_hello(who: &impl Greet) {
    who.greet();
}

fn main() {
    let alice = Person { name: "Alice".to_string() };
    let r2d2 = Robot { model: "R2-D2".to_string() };

    // 调用各自的 greet 方法(多态:同一方法名,不同实现)
    alice.greet();  // 输出:Hello, I'm Alice
    r2d2.greet();   // 输出:Greetings, human. I am R2-D2

    say_hello(&alice);  // 合法(Person 实现了 Greet)
    say_hello(&r2d2);   // 合法(Robot 实现了 Greet)
}
4. 与 “接口” 的区别

虽然类似其他语言的接口,trait 有更灵活的特性:

  • 可以为任何类型实现 trait(包括标准库类型,如 i32String 等);
  • 支持 “默认方法”,减少重复代码;
  • 可以通过 trait 组合实现复杂行为。

总之,trait 是 Rust 中定义行为规范的核心机制,它:

  • 规定了类型必须实现的方法;
  • 支持多态和代码复用;
  • 是 Rust 实现 “接口抽象” 的主要方式。

通过 trait,你可以写出更灵活、可扩展的代码,尤其是在需要处理多种类型但统一行为的场景(如通用算法、工具函数等)。

4.3 可变参数

Rust 支持可变参数(variable arguments),通常通过宏或标准库中的std::fmt::Arguments实现:

1. 使用宏定义可变参数函数

// 定义可变参数宏
macro_rules! sum {
    // 递归终止条件
    ($x:expr) => ($x);
    // 递归处理多个参数
    ($x:expr, $($y:expr),+) => ($x + sum!($($y),+));
}

fn main() {
    let s1 = sum!(1, 2, 3);
    let s2 = sum!(10, 20, 30, 40);
    println!("sum1: {}, sum2: {}", s1, s2);  // 输出:sum1: 6, sum2: 100
}

2. 使用std::fmt模块处理可变参数(类似println!

use std::fmt;

fn log(fmt: fmt::Arguments) {
    println!("[LOG] {}", fmt);
}

fn main() {
    let user = "admin";
    let action = "login";
    
    // 使用format_args!创建可变参数
    log(format_args!("用户 {} 执行了 {} 操作", user, action));
    // 输出:[LOG] 用户 admin 执行了 login 操作
}

4.4 递归函数

递归函数是调用自身的函数,在 Rust 中使用递归需要注意:

  • 必须有明确的终止条件
  • Rust 目前不支持尾递归优化,递归深度过大会导致栈溢出
// 计算阶乘(n! = n × (n-1) × ... × 1)
fn factorial(n: u64) -> u64 {
    // 终止条件
    if n <= 0 {
        1
    } else {
        n * factorial(n - 1)  // 递归调用
    }
}

// 斐波那契数列(F(n) = F(n-1) + F(n-2))
fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),  // 递归调用
    }
}

fn main() {
    println!("5的阶乘: {}", factorial(5));  // 输出:5的阶乘: 120
    println!("斐波那契数列第10项: {}", fibonacci(10));  // 输出:55
}

对于深度较大的递归,建议使用循环替代,避免栈溢出:

// 用循环实现阶乘(更安全)
fn factorial_iterative(n: u64) -> u64 {
    let mut result = 1;
    for i in 1..=n {
        result *= i;
    }
    result
}

fn main() {
    println!("5的阶乘: {}", factorial_iterative(5));  // 输出:5的阶乘: 120
}

五、代码块与作用域({} 作用域、变量遮蔽 Shadowing)

Rust 通过代码块(block)和作用域(scope)管理变量的生命周期,变量遮蔽是 Rust 中一个有特色的概念。

5.1 代码块与作用域

代码块用{}包围,创建一个新的作用域。变量在声明的作用域内有效,出了作用域会被自动释放(RAII 机制)。

fn main() {
    // 外部作用域
    let x = 10;
    println!("外部作用域的x: {}", x);  // 输出:外部作用域的x: 10
    
    {
        // 内部作用域
        let y = 20;
        println!("内部作用域的y: {}", y);  // 输出:内部作用域的y: 20
        println!("内部作用域可以访问外部变量x: {}", x);  // 输出:10
    }
    
    // 错误:y的作用域已结束
    // println!("外部作用域无法访问y: {}", y);  // 编译错误
    
    // 控制流结构(if/loop/for等)也会创建作用域
    if x > 5 {
        let z = 30;
        println!("if块内的z: {}", z);  // 输出:if块内的z: 30
    }
    // 错误:z的作用域已结束
    // println!("if块外无法访问z: {}", z);  // 编译错误
}

5.2 变量遮蔽(Shadowing)

变量遮蔽允许在同一作用域或内层作用域中用相同名称声明新变量,新变量会 "遮蔽" 旧变量。

fn main() {
    let x = 5;
    println!("x = {}", x);  // 输出:x = 5
    
    // 遮蔽:同一作用域中创建同名新变量
    let x = x + 1;
    println!("x = {}", x);  // 输出:x = 6
    
    // 遮蔽:改变变量类型
    let x = "现在我是字符串";
    println!("x = {}", x);  // 输出:x = 现在我是字符串
    
    // 内层作用域遮蔽外层变量
    {
        let x = 100;
        println!("内层作用域的x = {}", x);  // 输出:内层作用域的x = 100
    }
    
    // 内层作用域结束后,外层变量恢复可见
    println!("外层作用域的x = {}", x);  // 输出:外层作用域的x = 现在我是字符串
}

变量遮蔽的主要用途:

  • 转换变量类型同时保持名称一致
  • 临时修改变量值但不影响外部作用域
  • 简化代码(无需为相关变量创建不同名称)

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值