Rust学习笔记#4:语句和表达式

Stella981
• 阅读 729

Rust学习笔记#4:语句和表达式

严格的说,Rust中的所有东西只分为两类:表达式(Expression)和语句(Statement)。

  • 表达式:在维基百科的定义中,表达式是指由变量、常量和操作符等组合成的可求值的语法实体。
  • 语句:语句分为声明语句和表达式语句两种。声明语句用于声明变量、结构体、函数等各种项以及通过use等关键字引入包等。表达式语句,由一个表达式和一个分号组成,即在表达式后面加一个分号就将一个表达式转变为了一个语句。

可见,表达式在Rust中扮演了至关重要的角色,Rust基本上就是一个表达式语言。在Rust程序中,表达式可以是语句的一部分,反过来,语句也可以是表达式的一部分。一个表达式总是会产生一个值,因此它必然有类型;语句不产生值,它的类型永远是()。如果把一个表达式加上分号,那么它就变成了一个语句;如果把语句放到一个语句块中包起来,那么它就可以被当成一个表达式使用。

Rust有很多种表达式类型,具体可参见《Rust Reference》,点击这里。下面介绍几种常见的表达式。

表达式的副作用

从传统意义上讲,表达式的作用就是求值,它除了产生一个计算结果外,不应该改变参与计算过程的任何变量的值,这样的表达式称为无副作用的表达式。若一个表达式在求值过程中,改变了所使用的变量的值,则这样的表达式称为有副作用的表达式。例如:

// 没有改变任何变量的值,其为无副作用的表达式
5 + x 
// 改变了变量y的值,其为有副作用的表达式
y = x + 1 

表达式语句就是表达式副作用的重要应用。任何表达式加一个分号都可以作为一个语句来使用,但无副作用的表达式语句没有任何意义,例如5+x;这个表达式语句就没有意义。

运算表达式

运算表达式是含有运算符的表达式。运算符可分为以下几类:

  • 算术运算符:加(+),减(-),乘(*),除(/),求余(%
  • 比较运算符:等于(==),不等于(!=),小于(<),大于(>),小于等于(<=),大于等于(>=)。比较表达式的类型是bool
  • 逻辑运算符:逻辑与(&&),逻辑或(||),逻辑取反(
  • 位运算符:按位取反(),按位与(&),按位或(|),按位异或(^),左移(<<),右移(>>
  • 复合运算符: +, -, *, /, %, &, |, ^, <<>>都可以和=组成复合运算符

这些运算符的用法和C/C++中基本一致,需要注意的是以下两点:

  • Rust禁止连续比较,例如a == b == c这是错误的,必须要加上括号才行。这样设计对减少歧义有很大好处。
  • 按位取反和逻辑取反都是运算符!:如果被操作数是bool类型,则是逻辑取反,其他情况是按位取反。

关于运算符之间优先级的细微知识这里就不展开讲了,因为不论在哪种编程语言中,我都建议,如果碰到复杂的表达式,要使用小括号来明确表达计算顺序。

赋值表达式

一个左值表达式、赋值运算符(=)和右值表达式,可以构成一个赋值表达式。关于左值右值的知识可以参考《Rust学习笔记#2》。需要注意的是,赋值表达式的类型为(),这和C语言是不同的,C语言中赋值表达式的类型是左值表达式的类型。这样设计的好处有两点:

  • 防止连续赋值:x = y = z会产生编译错误,因为赋值运算符=要求两边的表达式同类型,而y = z()类型所以会产生编译错误。
  • 防止把==写成=:Rust中要求条件表达式的类型必须为bool,而赋值表达式的类型是(),会产生编译错误。

语句块表达式

语句块由{}构成,其类型是语句块中最后一个表达式的类型。也就是说,如果最后一个表达式带了分号,那么语句块的类型就是语句的类型();如果没带,就是表达式的类型。例如:

let x: () = {5;}; // x的类型为()
let x: i32 = {5}; // x的类型为i32

这样设计的好处是在写函数返回值时可以直接去掉最后一个语句的分号作为返回值,而不必写return。例如:

fn fun() -> i32 {
    100
}
// 等价于
fn fun() -> i32 {
    return 100;
}

条件表达式

if-else表达式在C语言中也有,我们这里讲一些不一样的地方:

  • if-else后必须要有大括号,不得省略,这样可以避免悬空else所导致的bug。

  • 条件表达式不需要用小括号括起来,如果加上小括号,编译器会提示这是一个多余的小括号。

  • if-else表达式的所有分支必须返回同一个类型的值,if-else的求值策略和语句块表达式相同。如果else分支省略,则默认else分支的类型为()。见下面的例子:

    let a = 1;
    let b = if a > 10 {
        a * 2
    } else {
        a * 3
    };
    println!("{}", b); // 输出:3
    

循环表达式

Rust中包括三种循环表达式:whileloopfor...in,其用法和其他编程语言相应的表达式基本类似,也可以使用continuebreak控制循环流程。注意,Rust中没有C语言中那种三段式for循环,这里的for...in本质上就是一个迭代器。

loop表示一个无限死循环,while是带条件判断的循环语句。注意,loopwhile true是不同的。相对于其他语言,Rust要做更多的静态分析,Rust认为while循环的条件可真可假,所以循环体里的表达式也会忽略不会进行分析,看下面的示例:

let x;
while true {
    x = 1;
    break;
}
println!("{}", x); // error[E0381]: borrow of possibly-uninitialized variable: `x`

Rust无法得知变量x的值在while循环块里被初始化过,从而会报使用未初始化变量的错。

match表达式

Rust提供了match表达式用于匹配各种情况,有点类似于C语言中的switch...case语句,但功能更加强大。先看一个简单的match示例:

enum Direction {
    East,
    West,
    South,
    North,
}

let x = Direction::East;
match x {
    Direction::East => println!("East"),
    Direction::West => println!("West"),
    Direction::South => println!("South"),
    Direction::North => println!("North"),
}
// 输出:East

上面的用法和switch...case语句很类似,下面讲讲不一样的地方:

  • exhaustive特性:exhaustive的意思是无遗漏的,也就是说,Rust要求match必须对所有的情况做完整的、无遗漏的匹配,如果漏掉了某些情况,是不能通过编译的。这样做的好处是可以强迫程序员对所有的情况进行考虑,从而减少bug的发生。

    // 同上面的代码
    match x { 
        Direction::East => println!("East"),
        Direction::West => println!("West"),
        Direction::South => println!("South"),
    }
    // error[E0004]: non-exhaustive patterns: `North` not covered
    
  • 下划线:当不想把每种情况一一列出时,可以用一个下划线来表达“除了列出来的那些之外的其他情况”。下划线存在的另外一个意义是,如果我们引用了他人的库中的某个enum类,但该类添加了新成员,这就会导致我们的代码编译失败。因此,不论何时,都推荐使用下划线作为容错措施。

    // 同上面的代码
    match x {
        Direction::East => println!("East"),
        _ => println!("Else"),
    }
    // 输出:East
    
  • match可以作为表达式,但要求其每一个分支都返回相同的类型。

    let x = Direction::East;
    let y = match x {
        Direction::East => 1,
        _ => 0,
    };
    println!("{}", y); // 输出:1
    
  • 可以使用范围作为匹配条件:

    let x = 'X';
    match x {
        'a'..='z' => println!("lowercase"),
        'A'..='Z' => println!("uppercase"),
        _ => println!("something else"),
    }
    // 输出:uppercase
    
  • 可以使用if作为匹配条件,当匹配成功且符合if条件时,才执行后面的语句:

    let x = Some(5);
    match x {
        Some(i) if i > 10 => println!("{}", i),
        _ => println!("Nothing!"),
    }
    // 输出:Nothing!
    

另外,Rust提供了if let语法糖来简化某些情况下的match表达式。如果我们有一个Option<T>类型的变量opt_val,如果我们需要取出里面的值,可以这样做:

match opt_val {
    Some(x) => {
        // handle x
    },
    _ => (),
}

但这样写比较冗长,而使用if let语法,可以这样做:

if let Some(x) = opt_val {
    // handle x
}

if let的语法为:if let PATTERN = EXPRESSION {BODY},它和match的区别是:它不需要完整匹配,只匹配感兴趣的某个特定分支即可。

参考文献

  • 《Rust编程之道》张汉东
  • 《深入浅出Rust》范长春
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
thinkphp3.2.3模板渲染支持三元表达式
thinkphp3.2.3模板渲染支持三元表达式{$status?'正常':'错误'}{$info'status'?$info'msg':$info'error'}注意:三元运算符中暂时不支持点语法。如下:           <divclass"modalhidefade"id'myModa
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
AJPFX总结关于Java中过滤出字母、数字和中文的正则表达式
1、Java中过滤出字母、数字和中文的正则表达式(1)过滤出字母的正则表达式\^(AZaz)\(2)过滤出数字的正则表达式\^(09)\(3)过滤出中文的正则表达式\^(\\\\u4e00\\\\u9fa5)\(4)过滤出字母、数字和中文的正则表达式\^(azAZ09\\\\u
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这