表达式语言

Rust 是所谓的表达式语言。这意味着它遵循更古老的传统,可以追溯到 Lisp^[1]^,在 Lisp^[1]^ 中,表达式能完成所有工作。

在 C 中,if 和 switch 是语句,它们不生成值,也不能在表达式中间使用。而在 Rust 中,if 和 match 可以生成值。

例子 1 Rust 中的 If 直接生成了一个值

let status =
    if cpu.temperature <= MAX_TEMP {
        HttpStatus::Ok
    } else {
        HttpStatus::ServerError  // 服务程序出错了
    };

优先级与结合性

查看 附录 表达式优先级对照表

例子 2

*   /   %   +   -   <<   >>   &   ^   |   &&   ||   as

块与分号

块是一种最通用的表达式。一个块生成一个值,并且可以在任何需要值的地方使用.

例子 3

let display_name = match post.author() {
    Some(author) => author.name(),
    None => {
        let network_info = post.get_network_metadata()?;
        let ip = network_info.client_address();
        ip.to_string()
    }
};

ip.to_string() 就是最终的返回值

例子 4

fn main() {
    let hello = {
        let world = "world";
        println!("Hello, {}!", world);
        format!("Hello, {}!", world)
    };
    println!("{}", hello);
    println!("{}", hello);
}

//输出
//Hello, world!
//Hello, world!
//Hello, world!


表示第一次 let hello 中的内容也会进行一次执行.

声明

除了表达式和分号,块还可以包含任意数量的声明。最常见的是 let 声明,它会声明局部变量.

let 声明可以在不初始化变量的情况下声明变量,然后再用赋值语句来初始化变量。这在某些情况下很有用,因为有时确实应该在某种控制流结构的中间初始化变量.

例子 5

let name;
if user.has_nickname() {
    name = user.nickname();
} else {
    name = generate_unique_name();
    user.register(&name);
}

你可能偶尔会看到似乎在重新声明现有变量的代码.这叫作遮蔽(shadowing)

例子 6

for line in file.lines() {
    let line = line?;
    ...
}

块还可以包含语法项声明(item declaration)。任何块都可能包含一个 fn.

例子 7

use std::io;
use std::cmp::Ordering;

fn show_files() -> io::Result<()> {
    let mut v = vec![];
    ...
    fn cmp_by_timestamp_then_name(a: &FileInfo, b: &FileInfo) -> Ordering {
        a.timestamp.cmp(&b.timestamp)   // 首先,比较时间戳
            .reverse()                  // 最新的文件优先
            .then(a.path.cmp(&b.path))  // 通过路径做二级比较
    }

    v.sort_by(cmp_by_timestamp_then_name);
    ...
}

ifmatch

match code match 表达式类似于 C 语言中的 switch 语句,但更灵活.通配符模式 _ 会匹配所有内容。

例子 8

match code {
    0 => println!("OK"),
    1 => println!("Wires Tangled"),
    2 => println!("User Asleep"),
    _ => println!("Unrecognized Error {}", code)
}

match 的多功能性源于每个分支 => 左侧支持的多种模式(pattern)。在上面的例子中,每个模式只是一个常量整数。我们还展示了用以区分两种 Option 值的 match 表达式

例子 9

match params.get("name") {
    Some(name) => println!("Hello, {}!", name),
    None => println!("Greetings, stranger.")
}

如果 expr 是一个块,则可以省略此分支之后的逗号。

[!IMPORTANT]

至少要有一个模式能够匹配。Rust 禁止执行未覆盖所有可能值的 match 表达式

ifmatch 表达式的所有块都必须生成相同类型的值.

例子 9

if let Some(cookie) = request.session_cookie {
    return restore_session(cookie);
}

if let Err(err) = show_cheesy_anti_robot_task() {
    log_robot_attempt(err);
    politely_accuse_user_of_being_a_robot();
} else {
    session.mark_as_human();
}

例子 10

for i in 0..20 {
    println!("{}", i);
}

使用 for ... in ... 会消耗掉目标对象,所有我们需要使用引用方式去使用该内容.

例子 11

let strings: Vec<String> = error_messages();
for s in strings {                  // 在这里,每个String都会转移给s……
    println!("{}", s);
}                                   // ……并在此丢弃
println!("{} error(s)", strings.len()); // 错误:使用了已移动出去的值
//改成这样
for rs in &strings {
    println!("String {:?} is at address {:p}.", *rs, rs);
}
//想要修改对象就是这样个样子
for rs in &mut strings {  // rs的类型是&mut String
    rs.push('\n');  // 为每个字符串添加一个换行
}

循环中的控制流

break 会退出当前循环.

例子 12

// 对`next_line`的每一次调用,或者返回一个`Some(line)`(这里的`line`是
// 输入中的一行),或者当输入已结束时返回`None`。最终会返回以"answer: "
// 开头的第1行,如果没找到,就返回"answer: nothing"
let answer = loop {
    if let Some(line) = next_line() {
        if line.starts_with("answer: ") {
            break line;
        }
    } else {
        break "answer: nothing";
    }
};

continue 会跳转到循环的下次迭代

例子 13

// 读取某些数据,每次一行
for line in input_lines {
    let trimmed = trim_comments_and_whitespace(line);
    if trimmed.is_empty() {
        // 跳转回循环的顶部,并移到输入中的下一行
        continue;
    }
    ...
}

循环可以带有生命周期标签。

例子 14

'search:
for room in apartment {
    for spot in room.hiding_spots() {
        if spot.contains(keys) {
            println!("Your keys are {} in the {}.", spot, room);
            break 'search;
        }
    }
}

return 表达式

return 表达式会退出当前函数,并向调用者返回一个值。

fn f() {     // 省略了返回类型:默认为()
    return;  // 省略了返回值:默认为()
}

为什么 Rust 中会有 loop

Rust 编译器中有几个部分会分析程序中的控制流。

  • Rust 会检查通过函数的每条路径是否返回了预期返回类型的值。为了正确地做到这一点,它需要知道是否有可能抵达函数的末尾。
  • Rust 会检查局部变量有没有在未初始化的情况下使用过。这就要检查通过函数的每一条路径,以确保只要不经过初始化此变量的代码,就无法抵达使用它的地方。
  • Rust 会对不可达代码发出警告。如果无法通过函数抵达某段代码,则这段代码不可达。

Rust 更倾向于简单性,它的流敏感分析根本不会检查循环条件,而会简单地假设程序中的任何条件都可以为真或为假。这会导致 Rust 可能拒绝某些安全程序.

fn wait_for_process(process: &mut Process) -> i32 {
  
    while true { 
        if process.wait() {
            return process.exit_code();
        }
    }
}  // 错误:类型不匹配:期待i32,实际找到了()

[!CAUTION]

这里的 while true

rust 编译时会去验证 while 为 false 时是怎么样的,然后就导致编译不通过导致整体程序异常.

!表示 exit() 永远不会返回,它是一个发散函数(divergent function)。

fn serve_forever(socket: ServerSocket, handler: ServerHandler) -> ! {
    socket.listen();
    loop {
        let s = socket.accept();
        handler.handle(s);
    }
}

函数与方法调用

Rust 中调用函数和方法的语法与许多其他语言中的语法相同

let x = gcd(1302, 462);  // 函数调用

let room = player.location();  // 方法调用

let mut numbers = Vec::new();  // 类型关联函数调用

server
    .bind("127.0.0.1:3000").expect("error binding server to address")
    .run().expect("error running server");

Rust 语法的怪癖之一就是,在函数调用或方法调用中,泛型类型的常用语法 Vec 是不起作用的:

return Vec<i32>::with_capacity(1000);  // 错误:是某种关于“链式比较”的错误消息

let ramp = (0 .. n).collect<Vec<i32>>();  // 同样的错误

// 下面就是改正内容
return Vec::with_capacity(10);  // 正确,只要fn的返回类型是Vec<i32>

let ramp: Vec<i32> = (0 .. n).collect();  // 正确,前面已给定变量的类型

字段与元素

你可以使用早已熟悉的语法访问结构体的字段。元组也一样,不过它们的字段是数值而不是名称.

game.black_pawns   // 结构体字段
coords.1           // 元组元素
pieces[i]          // 数组元素

这样的 3 个表达式叫作左值,因为赋值时它们可以出现在左侧.

game.black_pawns = 0x00ff0000_00000000_u64;
coords.1 = 0;
pieces[2] = Some(Piece::new(Black, Knight, coords));

从数组或向量中提取切片的写法很直观.

let second_half = &game_moves[midpoint .. end];

..      // RangeFull
a ..    // RangeFrom { start: a }
.. b    // RangeTo { end: b }
a .. b  // Range { start: a, end: b }

引用运算符

地址运算符 &&mut.

赋值

= 运算符用于给 mut 变量及其字段或元素赋值。但是赋值在 Rust 中不像在其他语言中那么常见,因为默认情况下变量是不可变的。

total += item.price; //复合赋值

[!CAUTION]

Rust 没有 C 的自增运算符 ++ 和自减运算符 --。

类型转换

在 Rust 中,将值从一种类型转换为另一种类型通常需要进行显式转换。

let x = 17;              // x是i32类型的
let index = x as usize;  // 转换成usize
  • &String 类型的值会自动转换为 &str 类型,无须强制转换。
  • &Vec 类型的值会自动转换为 &[i32]。
  • &Box 类型的值会自动转换为 &Chessboard。

闭包

Rust 也有 闭包,即轻量级的类似函数的值。闭包通常由一个参数列表组成,在两条竖线之间列出,后跟一个表达式:

let is_even = |x| x % 2 == 0;

let is_even = |x: u64| -> bool x % 2 == 0;  // 错误

let is_even = |x: u64| -> bool { x % 2 == 0 };  // 正确

assert_eq!(is_even(14), true);

附录

引用

  1. Lisp wiki - https://en.wikipedia.org/wiki/Lisp_(programming_language)

对照表

表达式优先级对照表

表达式类型 示例
数组字面量 [1, 2, 3]
数组重复表达式 [0; 50]
元组 (6, "crullers")
分组 (2 + 2)
{ f(); g() }
控制流表达式 if ok { f() }
if ok { 1 } else { 0 }
if let Some(x) = f() { x } else { 0 }
match x { None => 0, _ => 1 }
for v in e { f(v); }
while ok { ok = f(); }
while let Some(x) = it.next() { f(x); }
loop { next_event(); }
break
continue
return 0
宏调用 println!("ok")
路径 std::f64::consts::PI
结构体字面量 Point
元组字段访问 pair.0
结构体字段访问 point.x
方法调用 point.translate(50, 50)
函数调用 stdin()
索引 arr[0]
错误检查 create_dir("tmp")?
逻辑非 / 按位非 !ok
取负 -num
解引用 *ptr
借用 &val
类型转换 x as u32
n * 2
n / 2
取余(取模) n % 2
n + 1
n - 1
左移 n << 1
右移 n >> 1
按位与 n & 1
按位异或 n ^ 1
按位或 `n
小于 n < 1
小于等于 n <= 1
大于 n > 1
大于等于 n >= 1
等于 n == 1
等于 n != 1
逻辑与 x.ok && y.ok
逻辑与 `x.ok
右开区间范围 start .. stop
右闭区间范围 start ..= stop
赋值 x = val
复合赋值 x *=1
闭包 `