Rust流程控制

2022-06-01 09:08:11 浏览数 (1)

[TOC]


表达式的多种形式

语句?表达式?

语句在英文中是 statement,表达式则是 expression。我们可能常常听说过“赋值语句”或者“算数表达式”这些名词,但是你有想过为什么不是“赋值表达式”吗?语句和表达式有一个重要的区别在于,表达式总是返回一个值,而语句不会。例如:

代码语言:javascript复制
1   1;     // 这是表达式
let a = 1; // 这是语句

Rust 是一个基于表达式的语言,这意味着它的大多数代码都有一个返回值。除了以下几种语法:

  • 变量声明
  • 模块声明
  • 函数声明
  • 结构体声明
  • 枚举声明

你可能会奇怪为什么 if…else… 不在上面的列表中,事实上,在 Rust 中,条件与循环并不是语句,而是表达式,这意味着它可以有返回值!这可能是你首先会疑惑的地方:这看起来和 C 不太一样!

if 表达式,实现类似 C 语言中的三元表达式的功能:

代码语言:javascript复制
let cond = true;
let a = if cond {
    42
} else {
    24
};

loop 表达式的 break 语句后可跟着一个返回值返回:

代码语言:javascript复制
let mut s = 0;
let mut n = 10;
let a = loop {
    if n < 0 {
        break s;
    }
    s  = n;
    n -= 1;
};
println!("{:?}", a);

if-else选择结构

Rust 中的 if-else 语法与其他语言类似,与许多语言不同,if 后的布尔条件不需要用括号括起来。

如果使用 if-else 返回一个值,则所有分支必须返回相同的类型:

代码语言:javascript复制
fn main() {
    let n = 5;

    if n < 0 {
        print!("{} is negative", n);
    } else if n > 0 {
        print!("{} is positive", n);
    } else {
        print!("{} is zero", n);
    }

    let m = if n < 0 {
        2.0
    } else {
        3.0
    };
    println!("{}", m);
}

使用loop循环

Rust 提供了一个 loop 关键字来表示无限循环。

break 语句可用于随时退出循环,而 continue 语句可用于跳过其余的迭代并开始新的循环:

代码语言:javascript复制
// 计算 1   2   ...   100
fn main() {
    let mut sum = 0;
    let mut n = 0;
    loop {
        sum  = n;
        n  = 1;
        if n > 100 {
            break
        }
    }
    println!("{}", sum);
}

break 后可带上一个值,返回给一个变量,例如:

代码语言:javascript复制
fn main() {
    let mut counter = 0;

    let result = loop {
        counter  = 1;

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

    assert_eq!(result, 20);
}

上面这种写法一般用于重试操作。


使用while循环

while 是带循环条件的 loop。当条件为假时,结束循环。我们使用一个例子介绍 while 的语法。

fizzbuzz 是一个非常简单的编程任务,它的描述是:编写一个程序,打印从 1 到 100 的数字,对于 3 的倍数,打印 Fizz 而不是数字,对于 5 的倍数,打印 Buzz。

例如:

代码语言:javascript复制
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14,
Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26,
Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz, Fizz, ...

代码示例:

代码语言:javascript复制
fn main() {
    // A counter variable
    let mut n = 1;

    // Loop while `n` is less than 101
    while n < 101 {
        if n % 15 == 0 {
            println!("fizzbuzz");
        } else if n % 3 == 0 {
            println!("fizz");
        } else if n % 5 == 0 {
            println!("buzz");
        } else {
            println!("{}", n);
        }
        // Increment counter
        n  = 1;
    }
}

使用for_range进行迭代

Rust 中的 for … in … 语法可以用来遍历一个迭代器。有多种方式可以创建一个迭代器,最简单也是最常用的方式如下所示:

  • a..b:这将创建一个包含 a 而不包含 b,步长为 1 的迭代器。
  • a..=b:这将创建一个包含 a 且包含 b,步长为 1 的迭代器。
代码语言:javascript复制
fn main() {
    // 下面的代码将打印出 0, 1, 2, 3, 4
    for i in 0..5 {
        println!("{}", i);
    }
    // 下面的代码将打印出 0, 1, 2, 3, 4, 5
    for i in 0..=5 {
        println!("{}", i);
    }
}

for … in … 语法的第二个重要使用场景是遍历数组,但这需要我们首先将数组转换为一个迭代器,这可以通过 .iter().iter_mut() 实现,区别在于后者是可变的。

代码语言:javascript复制
fn main() {
    let mut myarray = [1, 2, 3];
    for i in myarray.iter() {
        println!("{}", i);
    }

    for i in myarray.iter_mut() {
        *i *= 2;
    }
    for i in myarray.iter() {
        println!("{}", i);
    }
}

Rust中的match

match 是 Rust 中的模式匹配语法,它允许开发者将一个值与一系列模式进行比较,然后根据模式匹配的结果执行特定的代码。它与其它语言中的 switch … case … 语法相近,但显然更加强大。在先前的课程中,我们已经知道 match 语法可以配合 enum 一起使用。

代码语言:javascript复制
enum Alphabet {
    A,
    B,
}

fn main() {
    let letter = Alphabet::A;

    match letter {
        Alphabet::A => {
            println!("It's A");
        }
        Alphabet::B => {
            println!("It's B")
        }
    }
}

另一方面,match 也经常用来匹配整型数据,例如当我们想知道一个 u8 整数是否是某几个特殊数字时:

代码语言:javascript复制
fn main() {
    let n: u8 = 42;

    match n {
        42 => {
            println!("bingo!")
        }
        _ => {
            println!("{}", n);
        }
    }
}

if_let语法糖

if let 是 Rust 中的一个语法糖,它主要简化了 match 操作。如果我们仅仅想当匹配发生时做某些操作,那么就可以使用 if let 替代 match。

例如当我们只想要变量 letter 为 A 时,打印消息,而忽略所有其它选项。可分别使用 match 或 if let 实现。

代码语言:javascript复制
enum Alphabet {
    A,
    B,
}

fn main() {
    let letter = Alphabet::A;

    match letter {
        Alphabet::A => {
            println!("It's A");
        }
        _ => {}
    }

    if let Alphabet::A = letter {
        println!("It's A");
    }
}

if let 同样可以匹配带参数的枚举

代码语言:javascript复制
enum Symbol {
    Char(char),
    Number,
}

fn main() {
    let symbol = Symbol::Char('A');

    if let Symbol::Char(char) = symbol {
        println!("{:?}", char);
    }
}

while_let语法糖

与 if let 相似的还有一个 while let 语法糖,只是 while let 语法糖很少被使用:

代码语言:javascript复制
enum Alphabet {
    A,
    B,
}

fn main() {
    let mut letter = Alphabet::A;

    while let Alphabet::A = letter {
        println!("It's A");
        letter = Alphabet::B;
    }
}

函数与方法

函数

函数的定义以 fn 开始,它的参数是带类型注释的,就像变量一样,如果函数返回值,则必须在箭头 -&gt; 之后指定返回类型。例如如下的斐波那契函数:

代码语言:javascript复制
fn fibonacci(n: u64) -> u64 {
    if n < 2 {
        return n;
    }
    return fibonacci(n - 1)   fibonacci(n - 2);
}

方法

方法是附加到对象的函数,这些方法可以通过 self 关键字访问对象及其其他方法的数据。方法在 impl 块下定义。访问对象中的方法有两种方式,如果方法带 self 参数,使用 . ,否则使用 :: 。示例:

代码语言:javascript复制
#[derive(Debug)]
struct Point {
    x: u64,
    y: u64,
}

impl Point {
    fn new(x: u64, y: u64) -> Point {
        Point { x, y }
    }

    fn get_x(&self) -> u64 {
        return self.x;
    }

    // 如果需要修改结构体中的数据, self 前面需要带上 mut
    fn set_x(&mut self, x: u64) {
        self.x = x;
    }
}

fn main() {
    let mut p = Point::new(1, 2);
    println!("{:?}", p);
    println!("{:?}", p.get_x());
    p.set_x(3);
    println!("{:?}", p.get_x());
}

函数与闭包

Rust 的闭包是一种匿名函数,它可以从它的上下文中捕获变量的值。闭包使用 || -&gt; 语法定义。闭包可以被保存在变量中:

代码语言:javascript复制
fn main() {
    let myclosures = |n: u32| -> u32 { n * 3 };
    println!("{}", myclosures(1))
}

move 关键字可以从闭包的运行环境中捕获值,它最常用的场景是将主线程中的一个变量传递到子线程中,如下所示:

代码语言:javascript复制
use std::thread;

fn main() {
    let hello_message = "Hello World!";

    thread::spawn(move || println!("{}", hello_message)).join();
}

高阶函数

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

在数学中它们也叫做算子(运算符)或泛函。高阶函数是函数式编程中非常重要的一个概念。

将函数作为参数传递:

代码语言:javascript复制
fn calc(method: fn(u32, u32) -> u32, a: u32, b: u32) -> u32 {
    method(a, b)
}

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

fn sub(a: u32, b: u32) -> u32 {
    a - b
}

fn main() {
    println!("{}", calc(add, 10, 20));
    println!("{}", calc(sub, 20, 10));
}

将函数作为返回值:

代码语言:javascript复制
fn calc(method: &str) -> fn(u32, u32) -> u32 {
    match method {
        "add" => add,
        "sub" => sub,
        _ => unimplemented!(),
    }
}

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

fn sub(a: u32, b: u32) -> u32 {
    a - b
}

fn main() {
    println!("{}", calc("add")(10, 20));
    println!("{}", calc("sub")(20, 10));
}

发散函数

发散函数永远不会被返回,它们的返回值被标记为 !,这是一个空类型。

代码语言:javascript复制
fn foo() -> ! {
    panic!("This call never returns.");
}

发散函数与空返回值函数不同,空返回值函数可以被返回:

代码语言:javascript复制
fn some_fn() {
    ()
}

fn main() {
    let a: () = some_fn();
    println!("This function returns and you can see this line.")
}

发散函数最大的用处是通过 Rust 的类型检查系统,例如,在前面的小节中我们知道 Rust 的 if-else 表达式必须返回相同的类型, 但是如果使用发散函数,下面的代码也是能通过编译的:

代码语言:javascript复制
fn foo() -> ! {
    panic!("This call never returns.");
}

fn main() {
    let a = if true {
        10
    } else {
        foo()
    };
    println!("{}", a);
}

实践:猜数字游戏

这道题来自 Rust 的官方文档。我们将实现一个经典的初学者编程问题:猜谜游戏。它的工作原理是:程序将生成一个介于 1 和 100 之间的随机整数,然后提示玩家输入猜测。输入猜测后,程序将指示猜测是过低还是过高。如果猜测正确,游戏将打印一条祝贺信息并退出。

需要使用到的一些工具:

  • 使用 rand 库生成随机数
  • 使用 std::io 标准库获取用户输入

课程代码:

代码语言:javascript复制
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..101);

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

0 人点赞