Rust 入门实战系列(7)- 控制流

1,518 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情

控制流程是所有编程语言的基础,今天我们继续 Rust 初学者实战系列,看看在 Rust 中条件语句,循环语句怎么使用。

条件语句

if 语句的用法很简单

  • if 关键字作为语句开头;
  • 判断条件的 condition 不要加括号,直接写 bool 表达式即可;
  • condition 后面跟一个花括号,里面放命中后的逻辑代码;
  • 如果有 else 语义,在第一个花括号后面跟上 else {},同样在花括号里写对应分支逻辑。

我们在 rust-learn 中用 cargo new controlflow 新建一个controlflow目录,把下面代码替换掉 main.rs

fn main() {
    let number = 3;

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

这里我们只是校验 number 是否小于 5,命中与否打印不同结果。

运行之后我们来看下结果

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was true

如果我们改一下,把 number 变成 9:

let number = 9;

再次运行,你会发现走了 else 分支,打印出来 condition was false

需要提一点的是,if 后面跟的 condition 一定需要是一个 bool 表达式,Rust 不像有一些语言支持用 0/1 直接代表 true/false 的语义,或者内部自动转换为 bool 类型,这样的用法是不行的。

多个条件用 else if

有的时候我们不止一个条件,希望判断多次,这个时候我们可以用多个 else if 来解决:

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");
    }
}

注意,Rust 没有类似 elif 这样的关键字,写的时候一定要明确的用 else if [condition] {} 来表达。一旦某个分支命中了,后面的 else if 其实 Rust 就不会看了,毕竟语义上它是 else。即便这里 number 是 6,的确对2,3取模都为0,但也只会命中第一个。

与 Golang 类似,Rust 对于多个 else if 这种略显冗余的写法也有一个类似 switch 语句的存在:match。我们会在后面的章节介绍。

在 let 语句中使用 if

if 可以作为表达式放到 let 赋值语句中,参照下面示例:

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

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

这里 if 和 else 都有返回值,这样的写法其实就是一个稍显冗余的三目运算符。运行之后输出以下结果

$ cargo run
===========================
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/branches`
The value of number is: 5

注意,这里 5, 6 两个值返回的时候都是按照 integer 默认的 i32 类型。这样用法一定要注意,类型要匹配,否则编译会报错。

循环语句

Rust 在循环控制方面提供了三种类型支持:loop, whilte, for。下面我们一个个来看:

loop

loop 的语义在于一直循环,如果你想退出,自己在 loop 里面 break 出来。

fn main() {
    loop {
        println!("again!");
    }
}

这样会无限制打 again! 这个字符串,直到我们手动 ctrl-c 停止项目。

和其他语言很类似,Rust 也有 continue 和 break。前者告诉 Rust 跳过这次循环里后面的代码,直接进入下一次循环,后者告诉 Rust 直接跳出循环。

还记得么?我们在此前 guessing game 里面就用过 break:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

当我们猜测的 guess 和 secret_number 相等时,从 loop 里 break 出来,不再循环。

从循环中返回数据

有时候我们希望从一个 loop 中返回数据给到上层调用,这里就遇到 Rust 独有的 feature 了。我们来看一下怎么搞:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

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

break 不仅仅是代表退出的语义,还可以类似 return 的作用,带着值退出。最后 loop 返回的值会赋给 result,我们执行一下会发现,最后 result 为 20。

给循环打标签

这一点很重要,也是 Rust 独家提供的功能,如果我们用 Golang,存在多层循环嵌套,想 break 出来其实不容易,因为每次 break 都只是最内层的循环,除非我直接 return 了,这样就非常不灵活。

Rust 支持你给 loop 打标签,之后就可以在使用 break 或者 continue 的时候指明生效的 loop 是哪个。标签需要用单引号开头。我们看个例子:

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}");
}

外层的 loop 被标记为 counting_up,它从 0 开始计数到 2,而内层的 loop 则是从高往低来计数。第一个 break 仅仅是退出了内层的 loop,而 break 'counting_up; 则会直接退出我们声明的这个外层 loop,直接执行下面的 println!("End count = {count}");

运行之后打印结果:

$ cargo run

===========================
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2

while

前面我们提到,loop 是没有条件的,无限循环,除非你 break 出去。有时候我们会需要先判断一些条件才能循环,这个时候就可以用 while,用法上和其他语言差别不大。

fn main() {
    let mut number = 3;

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

        number -= 1;
    }

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

只要 while 后面跟的 condition 为 true,循环就会继续,否则会退出。再看一个示例:

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

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

        index += 1;
    }
}

打印的结果如下:

$ cargo run
=======================
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50

for

上面我们给 while 的第二个例子其实是容易犯错的,因为 while 语句和数组长度是绑定的,修改一个也要修改另一个。假设现在数组变成了 4 个元素,但 while 没更新,就会出现 panic。这个时候其实建议用 for 循环。

for 循环对于集合类型的遍历很有用处,它也是实际上 Rust 生态中使用最多的循环类型:

fn main() {
    let a = [10, 20, 30, 40, 50];

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

这里的运行结果其实是一样的。而且发现没有,我们只需要 for xx in xx 就够了,语义就是针对集合里的每个元素,赋值,然后在循环体里做一些操作,你不用担心会不会出现 index 越界。

for 循环还可以搭配 range 数字生成器来使用:

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

小结

到目前位置,Rust 中最基础的概念,我们已经介绍完了,大家可以选择一些简单的算法题目练练手,试试看 Rust 这套流程是否好用。下一篇文章开始,我们会逐步了解 Rust 中最经典的概念:所有权。

感谢阅读,欢迎评论区交流!