Rust:函数与控制流

发布于:2025-08-29 ⋅ 阅读:(20) ⋅ 点赞:(0)


在编程中,函数和控制流是构建程序逻辑的核心要素。Rust 在这些基础概念上引入了许多独特的设计,比如表达式化的控制结构、严格的类型系统以及所有权机制。本文将从最基础的概念开始,逐步深入探讨 Rust 中的条件表达式、循环控制、函数定义以及各种返回机制。


条件表达式

if分支语句

if 是最基本的条件控制结构,用于根据条件执行不同的代码分支:

let number = 6;

if number > 5 {
    println!("数字大于5");
}

if number % 2 == 0 {
    println!("数字是偶数");
} else {
    println!("数字是奇数");
}

这是最传统的 if 用法,根据条件执行相应的代码块。当条件为真时,执行 if 后的代码块;当条件为假且有 else 分支时,执行 else 后的代码块。


if表达式化

与其他语言不同,Rust 中的 if 不仅是语句,更是表达式,可以返回值

let number = 6;

let result = if number % 2 == 0 {
    "偶数"
} else {
    "奇数"
};

println!("数字 {} 是 {}", number, result);

这里的关键概念是:代码块 {} 的最后一个表达式就是该代码块的返回值。注意 "偶数""奇数" 后面都没有分号,这意味着它们是表达式而不是语句,会作为代码块的返回值。


if表达式的类型要求

由于 if 是表达式,编译器需要在编译时确定其类型,因此所有分支必须返回相同类型

let condition = true;

// 正确:所有分支返回相同类型
let number = if condition {
    5
} else {
    6
};

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

// 错误示例(会编译失败):
let mixed = if condition {
    5        // 整数类型
} else {
    "six"    // 字符串类型
};

这个限制确保了类型安全,编译器可以在编译时就确定变量的类型,避免运行时的类型错误。


match分支匹配

match 是 Rust 中更强大的分支控制结构,可以匹配多种模式:

let number = 3;

match number {
    1 => println!("一"),
    2 => println!("二"),
    3 => println!("三"),
    _ => println!("其他"),
}

match 通过模式匹配来决定执行哪个分支。每个分支由模式和对应的代码组成,用 => 连接。_ 是通配符,匹配所有其他情况。


match的穷尽性要求

match 必须覆盖被匹配值的所有可能情况,这被称为"穷尽性"要求:

enum Direction {
    North,
    South,
    East,
    West,
}

let dir = Direction::North;

match dir {
    Direction::North => "向北前进",
    Direction::South => "向南前进",
    Direction::East => "向东前进",
    Direction::West => "向西前进",
    // 如果缺少任何一个枚举值,编译器会报错
}

穷尽性检查是编译时进行的,确保你不会遗漏任何情况。这大大减少了运行时错误的可能性。


match的模式匹配

match 支持多种复杂的模式匹配:

let number = 7;

match number {
    1 => println!("一"),
    2 | 3 => println!("二或三"),          // 多值匹配
    4..=6 => println!("四到六"),          // 范围匹配
    _ => println!("其他"),
}
  • 多值匹配:使用 | 可以匹配多个值
  • 范围匹配:使用 ..= 可以匹配一个范围内的值

模式守卫

模式守卫允许在匹配模式的基础上添加额外的条件:

let number = 15;

match number {
    n if n > 20 => println!("大于20: {}", n),
    n if n > 10 => println!("大于10但小于等于20: {}", n),
    n if n > 0 => println!("大于0但小于等于10: {}", n),
    0 => println!("恰好是零"),
    _ => println!("负数或其他"),
}

模式守卫使用 变量 if 条件 的形式,在模式匹配成功后,还要满足 if 后的条件才会执行对应分支,如果不匹配,那么向下继续匹配


match表达式返回值

if 类似,match 也是表达式,可以返回值

let number = 3;

let description = match number {
    1 => "一",
    2 => "二", 
    3 => "三",
    _ => "其他",
};

println!("数字 {} 对应 {}", number, description);

所有分支必须返回相同类型的值,这样整个 match 表达式才有确定的类型


循环控制

loop无限循环

loop 创建一个无限循环,是最基本的循环结构:

let mut counter = 0;

loop {
    counter += 1;
    println!("计数: {}", counter);
    
    if counter == 5 {
        break;
    }
}

println!("循环结束");

loop 会无限执行,只有通过 break 才能退出循环。这是最直接的循环控制方式,适用于需要明确控制退出条件的场景。


break和continue

在循环中,break 用于退出循环,continue 用于跳过本次迭代:

let mut count = 0;

loop {
    count += 1;
    
    if count % 2 == 0 {
        continue;  // 跳过偶数
    }
    
    if count > 10 {
        break;     // 超过10就退出
    }
    
    println!("奇数: {}", count);
}

continue 会跳过当前循环的剩余代码,直接进入下一次循环break 会立即退出整个循环


loop表达式返回值

loop 也是表达式,可以通过 break 返回值

let mut num = 1;

let result = loop {
    if num % 2 == 0 {
        break num;  // 返回 num 的值
    }
    num += 1;
};

println!("第一个偶数: {}", result);

这里 break num 表示退出循环并返回 num 的值。整个 loop 表达式的值就是 break 后面的值。


while条件循环

while 循环在条件为真时持续执行:

let mut number = 5;

while number > 0 {
    println!("倒计时: {}", number);
    number -= 1;
}

println!("发射!");

while 在每次循环开始前检查条件,条件为假时退出循环。这比 loop + if + break 的组合更简洁。


while循环中的break

while 循环中也可以使用 break 提前退出,但与 loop 不同的是,while 循环不允许 break 携带返回值

let mut count = 0;

while count < 10 {
    count += 1;
    
    if count == 5 {
        break;  // 可以 break,但不能 break 值
    }
    
    println!("计数: {}", count);
}

let result = while condition { break 42; };  // 错误!while 不能返回值

这是因为 while 循环的条件可能一开始就为假,那样循环体根本不会执行,无法确定返回值


for迭代器循环

for 循环用于遍历集合或迭代器:

let numbers = [1, 2, 3, 4, 5];

for num in numbers {
    println!("数字: {}", num);
}

for 循环会自动遍历可迭代对象的每个元素。这是 Rust 中最常用的循环方式,既安全又高效。


for循环获取索引

有时需要在遍历时获取元素的索引:

let fruits = ["苹果", "香蕉", "橙子"];

for (index, fruit) in fruits.iter().enumerate() {
    println!("{}: {}", index, fruit);
}

enumerate() 方法返回 (索引, 元素) 的元组,让你可以同时访问索引和元素值。


标签跳转

当有嵌套循环时,可以使用标签来指定 breakcontinue 要影响哪个循环:

'outer: loop {
    println!("进入外层循环");
    
    'inner: loop {
        println!("进入内层循环");
        break 'outer;  // 跳出外层循环
    }
    
    println!("这行代码不会执行");
}

println!("退出所有循环");

标签以单引号开头,可以让 breakcontinue 作用于指定的循环。这在复杂的嵌套循环中很有用。

当使用标签跳转时,break 也可以携带返回值,但只能在 loop 循环中使用

let result = 'outer: loop {
    let mut inner_count = 0;
    
    'inner: loop {
        inner_count += 1;
        println!("内层计数: {}", inner_count);
        
        if inner_count == 3 {
            break 'outer inner_count * 10;  // 跳出外层循环并返回值
        }
        
        if inner_count > 5 {
            break 'inner;  // 只跳出内层循环
        }
    }
    
    println!("这行代码不会执行");
};

println!("返回值: {}", result);  // 输出: 返回值: 30

标签跳转的返回值只能用于 loop 循环,whilefor 循环即使使用标签也不能返回值


函数签名与返回值

基本函数定义

函数是封装代码逻辑的基本单位:

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

greet("World");

函数使用 fn 关键字定义,函数名后跟参数列表,参数需要指定类型


带返回值的函数

函数可以返回值,需要在参数列表后指定返回类型:

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

let result = add(5, 3);
println!("5 + 3 = {}", result);

使用 -> 指定返回类型,函数体最后一个表达式作为返回值

对于包含多个分支的函数,每个分支的最后一个表达式都可以作为该分支的返回值

fn calculate(x: i32) -> i32 {
    let doubled = x * 2;
    
    if doubled > 10 {
        doubled - 5    // 这个分支的返回值
    } else {
        doubled + 5    // 这个分支的返回值
    }
    // 整个 if 表达式的值作为函数返回值
}

fn classify_number(n: i32) -> &'static str {
    match n {
        0 => "零",           // 这个分支的返回值
        1..=10 => "小数",    // 这个分支的返回值
        11..=100 => "中数",  // 这个分支的返回值
        _ => "大数",         // 这个分支的返回值
    }
    // 整个 match 表达式的值作为函数返回值
}

函数体最后一个表达式(这里是整个 ifmatch 表达式)自动作为返回值,不需要显式的 return


return

虽然 Rust 支持隐式返回,但有时需要在函数中间提前返回:

fn check_positive(x: i32) -> i32 {
    if x <= 0 {
        return 0;  // 提前返回
    }
    
    x * 2  // 正常返回
}

return 关键字可以在函数的任何位置提前返回值并退出函数


单元类型()

当函数不需要返回有意义的值时,返回单元类型 ()

fn print_info(msg: &str) {
    println!("信息: {}", msg);
    // 隐式返回 ()
}

fn do_something() -> () {
    println!("执行某些操作");
    // 显式指定返回 (),但通常不需要
}

单元类型 () 表示"无意义的值",类似于其他语言中的 void。不返回值的函数实际上返回 ()


参数传递

函数可以接受参数:

fn display_info(name: &str, age: i32, height: f64) {
    println!("姓名: {}", name);
    println!("年龄: {} 岁", age);
    println!("身高: {:.1} 米", height);
}

fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

函数参数按顺序传递,每个参数都必须指定类型


数组参数

可以将数组作为参数传递:

fn print_array(arr: [i32; 5]) {
    for element in arr {
        println!("{}", element);
    }
}

fn sum_array(arr: [i32; 3]) -> i32 {
    let mut total = 0;
    for element in arr {
        total += element;
    }
    total
}

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    print_array(numbers);
    
    let small_array = [10, 20, 30];
    let sum = sum_array(small_array);
    println!("数组总和: {}", sum);
}

数组参数需要指定确切的大小,例如 [i32; 5] 表示包含5个i32元素的数组


参数模式匹配

函数参数可以使用模式匹配直接解构:

// 解构元组参数
fn print_point((x, y): (i32, i32)) {
    println!("坐标: ({}, {})", x, y);
}

// 解构数组的前几个元素
fn print_first_two([first, second, ..]: [i32; 5]) {
    println!("前两个元素: {} 和 {}", first, second);
}

参数模式匹配让你可以直接在函数签名中解构复杂类型,使代码更简洁。


发散函数!

发散函数是永远不会正常返回的函数,返回类型是 !

fn crash() -> ! {
    panic!("程序崩溃了");
}

fn infinite_loop() -> ! {
    loop {
        println!("永远运行...");
    }
}

! 类型表示"never",意味着函数永远不会正常返回。常见的发散函数包括 panic!、无限循环等。

类型兼容性

一般来说,我们不会把!直接作为返回值,这个类型的特殊之处在于它可以强制转换为任何类型:

fn safe_divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        handle_error("除零错误");  // ! 类型,但兼容 i32
    } else {
        a / b                      // i32 类型
    }
    // 整个 if 表达式的类型是 i32
}

! 类型可以与任何类型兼容,这使得在某些分支中使用 panic!exit 变得很自然,哪怕函数要求返回 i32 等其他类型,你也可以返回一个 ! 来表示一个错误。