天天看点

Rust流程控制

表达式的多种形式

语句?表达式?

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

1 + 1;     // 这是表达式
let a = 1; // 这是语句
           

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

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

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

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

let cond = true;
let a = if cond {
    42
} else {
    24
};
           

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

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 返回一个值,则所有分支必须返回相同的类型:

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 语句可用于跳过其余的迭代并开始新的循环:

// 计算 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 后可带上一个值,返回给一个变量,例如:

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。

例如:

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, ...
           

代码示例:

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 的迭代器。
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()

实现,区别在于后者是可变的。

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 一起使用。

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 整数是否是某几个特殊数字时:

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 实现。

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 同样可以匹配带参数的枚举

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 语法糖很少被使用:

enum Alphabet {
    A,
    B,
}

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

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

函数与方法

函数

函数的定义以 fn 开始,它的参数是带类型注释的,就像变量一样,如果函数返回值,则必须在箭头

->

之后指定返回类型。例如如下的斐波那契函数:

fn fibonacci(n: u64) -> u64 {
    if n < 2 {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}
           

方法

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

.

,否则使用

::

。示例:

#[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 的闭包是一种匿名函数,它可以从它的上下文中捕获变量的值。闭包使用

|| ->

语法定义。闭包可以被保存在变量中:

fn main() {
    let myclosures = |n: u32| -> u32 { n * 3 };
    println!("{}", myclosures(1))
}
           

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

use std::thread;

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

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

高阶函数

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

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

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

将函数作为参数传递:

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

将函数作为返回值:

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

发散函数

发散函数永远不会被返回,它们的返回值被标记为

!

,这是一个空类型。

fn foo() -> ! {
    panic!("This call never returns.");
}
           

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

fn some_fn() {
    ()
}

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

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

fn foo() -> ! {
    panic!("This call never returns.");
}

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

实践:猜数字游戏

  • 使用

    rand

    库生成随机数
  • 使用

    std::io

    标准库获取用户输入
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;
            }
        }
    }
}
           

继续阅读