Rust 学习笔记:函数和控制流

Rust 学习笔记:函数和控制流

Rust 学习笔记:函数和控制流

本篇文章介绍 Rust 的函数、注释和控制流。

函数(Function)

fn 关键字允许声明新函数。main 函数是程序的入口点。

在 Rust 中定义函数时,输入 fn,后跟函数名和一组圆括号。花括号告诉编译器函数体的开始和结束位置。

Rust 并不关心你在哪里定义你的函数,只关心它们被定义在调用者可以看到的作用域中。

示例:

rust 复制代码
fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

运行结果:

我们可以将函数定义为具有形参的函数,形参是作为函数签名一部分的特殊变量。

示例:

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

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

运行结果:

在函数定义中,必须声明每个参数的类型。当定义多个参数时,用逗号分隔参数声明。

示例:

rust 复制代码
fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

运行结果:

语句和表达式

在 Rust 中,函数体由一系列语句组成,可以由表达式结尾。

  • 语句是执行某些操作的指令,但不返回值。
  • 表达式计算并返回一个结果值。

函数定义也是语句。

创建变量并使用 let 关键字为其赋值是语句。因为语句不返回值,所以不能将 let 语句赋值给另一个变量。

示例:

rust 复制代码
fn main() {
    let x = (let y = 6);
}

运行报错:

let y = 6语句不返回值,因此没有任何东西可以绑定 x。这与其他语言(如 C)中的情况不同,在这些语言中,赋值返回赋值的值。在这些语言中,你可以写 x = y = 6,让 x 和 y 的值都是 6,但在 Rust 中却不是这样。

表达式的计算结果是一个值,并且构成了在 Rust 中编写的大部分其余代码。考虑一个数学运算,比如 5 + 6,它是一个计算值为 11 的表达式。表达式可以是语句的一部分,例如语句 let y = 6 中的 6 是求值为 6 的表达式。调用函数是一个表达式。调用宏是一个表达式。用大括号创建的新作用域块是一个表达式。

示例:

rust 复制代码
fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

表达式

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

是一个块,在这个例子中,它的值是 4。作为 let 语句的一部分,这个值被绑定到 y 上。注意,x + 1 行末尾没有分号,这与目前看到的大多数行不同。

表达式不包括结束分号。如果在表达式的末尾添加分号,则将其转换为语句,然后它将不返回值。

带返回值的函数

函数可以向调用它们的代码返回值。我们不为返回值命名,但必须在箭头(->)后面声明它们的类型。在 Rust 中,函数的返回值与函数体块中最终表达式的值是同义的。通过使用 return 关键字并指定一个值,可以提前从函数返回,但是大多数函数隐式地返回最后一个表达式。下面是一个返回值的函数示例:

示例:

rust 复制代码
fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

在 five 函数中没有函数调用、宏,甚至没有 let 语句------只有数字 5 本身。这在 Rust 中是一个完全有效的函数。注意,函数的返回类型也被指定为 i32。

另一个示例:

rust 复制代码
fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

运行这段代码将打印 x 的值为 6。但是,如果我们在包含 x + 1 的行末尾放置一个分号,将其从表达式更改为语句,程序将报错。

注释

在 Rust 中,惯用的注释样式以两个斜杠开始注释,并且注释一直持续到行尾。对于超出单行的注释,你需要在每一行都包含 //,像这样:

rust 复制代码
// So we're doing something complicated here, long enough that we need
// multiple lines of comments to do it! Whew! Hopefully, this comment will
// explain what's going on.

注释可以放在它所注释的代码上方的单独一行,也可以放在包含以下代码的行尾。

rust 复制代码
fn main() {
	// I'm feeling lucky today
    let lucky_number = 7; // I'm feeling lucky today
}

控制流

在大多数编程语言中,根据条件是否为真来运行某些代码以及在条件为真时重复运行某些代码的能力是基本的构建块。Rust 代码执行流的最常见结构是 if 表达式和循环。

if 表达式

if 表达式允许根据条件对代码进行分支。提供一个条件,然后声明:"如果满足此条件,则运行此代码块;如果条件不满足,就不要运行这段代码。"

示例:

rust 复制代码
fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

运行结果:

同样值得注意的是,这段代码中的条件必须是 bool 类型。如果条件不是 bool 类型,我们将得到一个错误。例如,尝试运行以下代码:

rust 复制代码
fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

报错:

这个错误表明 Rust 期望的是 bool 值,但得到的却是整数。Rust 不会自动尝试将非布尔类型转换为布尔类型,必须明确地提供一个布尔值作为它的条件。

使用 else if 处理多个条件

可以通过在 else if 表达式中组合 if 和 else 来使用多个条件。

示例:

rust 复制代码
fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

当这个程序执行时,它依次检查每个 if 表达式,并执行条件为 true 的第一个语句体。

在 let 语句中使用 if

因为 if 是一个表达式,我们可以在 let 语句的右侧使用它将结果赋值给一个变量。

正确的示例:

rust 复制代码
fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

有可能成为 if 的每个分支的结果的值必须是相同的类型,如果类型不匹配,将会报错。

错误的示例:

rust 复制代码
fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {number}");
}

报错信息:

if 块中的表达式求值为整数,else 块中的表达式求值为字符串。这行不通,因为变量必须有单一类型,Rust 需要在编译时明确地知道 number 变量是什么类型。

循环

Rust 有三种循环:loop、while 和 for。我们每个都试试。

loop

loop 关键字将无限循环,直到显式停止程序。

示例:

rust 复制代码
fn main() {
    loop {
        println!("again!");
    }
}

大多数终端都支持键盘快捷键 Ctrl+C 来中断陷入连续循环的程序。

幸运的是,Rust 还提供了一种使用代码跳出循环的方法。可以在循环中放置 break 关键字,以告诉程序何时停止执行循环。

continue 关键字则在循环中告诉程序跳过此循环迭代中的任何剩余代码,并进入下一个迭代。

从循环中返回值

可以在用于停止循环的 break 表达式之后添加想要返回的值,该值将从循环中返回。

示例:

rust 复制代码
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

运行结果:

在循环之前,声明一个名为 counter 的变量,并将其初始化为 0。然后声明一个名为 result 的变量来保存循环返回的值。在循环的每次迭代中,我们给计数器变量加 1,然后检查计数器是否等于 10。如果是,则使用 break 关键字和值 counter * 2。循环结束后,使用分号结束将值赋给 result 的语句。最后,在 result 中打印值,在本例中为 20。

也可以从循环内部返回。break 只退出当前循环,而 return 总是退出当前函数。

循环标签消除多个循环之间的歧义

如果循环中有循环,则在该点中断并继续应用于最内层的循环。可以选择在循环上指定一个循环标签,然后可以使用 break 或 continue 来指定这些关键字应用于有标签的循环,而不是最内层的循环。循环标签必须以单引号开始。下面是两个嵌套循环的例子:

rust 复制代码
fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

外部循环具有 'counting_up' 标签,它将从 0 数到 2。没有标签的内部循环从 10 数到 9。第一个没有指定标签的 break 只会退出内循环。

程序打印:

复制代码
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
带 while 的条件循环

示例:

rust 复制代码
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

这种结构消除了在使用 loop、if、else 和 break 时所必需的大量嵌套,并且更加清晰。当条件求值为 true 时,代码运行;否则,退出循环。

还可以使用 while 构造遍历集合(如数组)的元素。

示例:

rust 复制代码
fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index += 1;
    }
}

这里,代码对数组中的元素进行计数。它从索引 0 开始,然后循环直到到达数组中的最终索引(也就是说,当 index < 5 不再为真时)。

然而,这种方法容易出错。如果索引值或测试条件不正确,我们可能会导致程序报错。例如,如果将 a 数组的定义更改为包含 4 个元素,但忘记将条件更新为 while index < 4,则代码将出现问题。它也很慢,因为编译器会添加运行时代码,在循环的每次迭代中执行索引是否在数组边界内的条件检查。

使用 for 循环遍历集合

作为一种更简洁的替代方法,可以使用 for 循环并为集合中的每个项执行一些代码。

示例:

rust 复制代码
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

这样做提高了代码的安全性,并消除了可能由于超出数组的末尾或不够远而丢失某些项而导致的错误的可能性。

相关推荐
Rust研习社17 分钟前
Rust 的 move 语义,一次讲透
后端·rust·编程语言
WMYeah4 小时前
【无标题】
前端·rust·抽奖程序·跨平台抽奖程序
楼兰公子18 小时前
buildroot 在编译rust时裁剪平台类型数量的方法
开发语言·后端·rust
Rust研习社1 天前
开源项目里的 deny.toml 是什么?
后端·rust·编程语言
铭毅天下1 天前
当搜索引擎遇上 Rust——深度解读下一代实时搜索引擎 INFINI Pizza
开发语言·后端·搜索引擎·rust
咸甜适中1 天前
rust语言学习笔记Trait之Default(默认值)
笔记·学习·rust
容智信息2 天前
AI Agent(智能体)的输出格式应该从 Markdown 转向 HTML吗?
前端·人工智能·rust·编辑器·html·prompt
Rust研习社2 天前
Rust Clippy 实用指南:写出更优雅、安全的 Rust 代码
后端·rust·编程语言
yangyongdehao302 天前
两天用AI+rust撸了一款本地批量去水印软件,30MB,效果能打
ai作画·rust