LISP程序员知道一切的价值, 但不了解其代价. ----Alan Perlis

6.1 表达式语言

Rust是表达式语言, 这意味着:

  • 虽然Rust严格区分语句(Statement)和表达式(Expression)
  • 但是所有的代码结构都可以是表达式(Expression), 也可以转化为语句(Statement)从而抛弃表达式返回的值
    • 比如: if在C语言中没有返回值 (用Rust的思维来理解: 强制为statement), 但是在Rust中可以是表达式

这也是Rust没有三元运算符的原因: 可以直接用if表达式代替

fn main() {
    let status = if (true) {
        1
    } else {
        0
    };
}

6.2 优先级与结合性

结合性: 所有可以链式书写的运算符都是左结合的

* / % + - << >> & ^ | && || as均为左结合

重点优先级关系:

  • 访问运算符. > 解引用运算符*
  • 算数运算符 > 位运算符 > 比较运算符 > 逻辑运算符 > 赋值运算符
表达式类型示例相关特性
数组字面量[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
std::iter::IntoIterator
宏调用println!("ok")
路径std::f64::consts::PI
结构体字面量Point {x: 0, y: 0}
元组字段访问pair.0Deref, DerefMut
结构体字段访问point.xDeref, DerefMut
方法调用point.translate(50, 50)Deref, DerefMut
函数调用stdin()Fn(Arg0, ...) -> T, FnMut(Arg0, ...) -> T, FnOnce(Arg0, ...) -> T
索引arr[0]Index, IndexMutDeref, DerefMut
错误检查create_dir("tmp")?
逻辑非/按位非!okNot
取负-numNeg
解引用*ptrDeref, DerefMut
借用&val
类型转换x as u32
n * 2Mul
n / 2Div
取余(取模)n % 2Rem
n + 1Add
n - 1Sub
左移n << 1Shl
右移n >> 1Shr
按位与n & 1BitAnd
按位异或n ^ 1BitXor
按位或n | 1BitOr
小于n < 1std::cmp::PartialOrd
小于等于n <= 1std::cmp::PartialOrd
大于n > 1std::cmp::PartialOrd
大于等于n >= 1std::cmp::PartialOrd
等于n == 1std::cmp::PartialEq
不等于n != 1std::cmp::PartialEq
逻辑与x.ok && y.ok
逻辑或`x.ok
闭包|x, y| x + y

6.3 块与分号

  • 块本质: 表达式 (一个块生成一个值)
  • 块的值: 块表达式的值 = 最后一个表达式的值 (类似于函数返回)
    • 块中表达式均有;, 返回()
  • 分号:
    • lei声明必须带分号
    • 带分号表达式: 调用方法, 丢弃返回值
  • Some(anthor) =>后续是一个普通表达式
  • None =>后续是块表达式, 块表达式的值 = 最后一个表达式的值
fn main() {
    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()
        }
    }
}

使用if返回值必须包含else, 保证变量始终能接受确定的值

if preference.changed() {
    page.compute_size()
} // 错误: 缺少else

6.4 声明

变量名约定:

  • 可以包含更多的字符, 包含希腊字母, 中文
  • 不允许使用表情符号
  • let声明可以不初始化 (编写过程中vscode实时检查会报错)
    • let允许对变量赋值一次(就是初始化), 所以后续无需let mut也可以赋值一次
let 变量名: 类型 = 表达式;

6.4.1 遮蔽 (shadowing)

处理遮蔽: 内部遮蔽外部

for line in file.lines() { // 外部line
    let line = line?; // 内部声明line, 从此出开始, 后续内部line遮蔽外部
    ...
}

6.4.1 语法项声明

  • 语法项(syntax items): 可以在程序或模块中的任意地方出现的声明 (比如: fn, struct, use)
  • 语法项声明(item declaration): rust可以在块中语法项声明, 比如在块中通过fn声明一个函数. (其作用域只能在块内)

6.5 if, match与循环

6.5.1 if语句

语法约定:

  • condition1必须是bool型
  • if不需要圆括号
  • else ifelse可选
  • 块的本质是表达式, if表达式的块必须生成相同类型的值, 如果没有else或者没有返回值, 默认生成零元组()
if condition1 {
    block1
} else if condition2 {
    block2
} else {
    block_n
}

6.5.2 match

语法约定:

  • 类似于switch
    • 更优秀的点: 左侧支持多种pattern, 而不仅仅只是常量 (比如: Option<T>, 解构元组, 元组各个字段等)
  • 最大的不同: =>后续是表达式(Expression), 而不是单独的语句(Statement), 并使用逗号分隔
  • 限制: 与if类似, 返回的表达式必须是相同类型的
  • 基本语法:
    • default改为_
    • :改为=>
    • 优先级: 从上到下递减, 所以不能将_放在靠前的位置, 导致优先匹配_
// 一般格式

match value {
    pattern => expression,
    ...
}

// 实例

match flag {
    0 => println!("OK"),
    1 => println!("Wires Tangled"),
    _ => println!("Error")
}

6.5.3 if let

  • 本质: match的简写
  • if和if let的区别: pattern可以进行模式匹配 (实现: Option或者Result提取数据)
// if-let

if let pattern = expr {
    block1
} else {
    block2
}

// 等效match
match expr {
    pattern => { block1 }
    _ => { block2 }
}

6.5.4 循环

四种循环

  • 其中..表示生成一个range, 即一个结构体(start, end) (比如: 0..20表示std::ops::Rangg {start: 0, end: 20}Range是可迭代类型)
while condition {
    block
} // 本质表达式: 返回()

while let pattern = expr {
    block
} // 本质表达式: 返回()

loop { // 无限循环: 直到遇到break或者return
    block
} // 本质表达式: 如果指定一个值, loop能生成该值

for pattern in iterable { // iterable表示可迭代
    block
} // 本质表达式: 返回()

// 实例

for i in 0..20 { ... }

6.5.4.a for循环特性

for循环会消耗值

fn main() {
    let strings: Vec<String> = vec!["Lame".to_string(), "Crow".to_string()];

    for s in strings { // 相当于s = strings[i]
        println!("{}", s);
    }

    println!("{} error(s)", strings.len()); // 错误: strings以及下属的元素的所有权均move给了for循环, 并消耗掉了
}

利用reference隐藏该特性

for rs in &strings {
    println!("String {:?} is at address {:p}.", *rs, rs);
}

6.6 循环中的控制流

6.6.1 break和continue

  • rust中的控制表达式: break, continue (注意: break, continue等也是表达式)
  • loopbreak联合: break的功能类似于return
//break

fn main() {
    let a = 1;

    let answer = loop {
        if a == 1 {
            break 111;
        } else {
            break 222;
        }
    };
}

// continue

fn main() {
    for i in 0..50 {
        if i % 2 == 0 {
            continue;
        }

        println!("{}", i);
    }
}

6.6.2 多重break和循环生命周期

通过给循环标注生命周期实现多重break

fn main() {
    'ending: for i in 0..20 { // 生命周期标注
        for j in 0..20 {
            if i == 10 && j == 15 {
                break 'ending 1; // 跳出外层for循环, 同时返回1
            }

            println!("({}, {})", i, j);
        }
    }
}

6.7 return表达式

  • 默认返回: 或者默认情况返回()
  • 带值返回: 尾行省略分号, 直接给出表达式, 表示返回
  • 上述两种情况都不需要使用return, return主要用于提前返回的情况

6.8 异常控制流与!类型

  • Rust采用流敏感(flow-sensitive)分析, 会检查每一个执行的分支.
  • 且检查规则偏于简单性: 每个循环条件都会假设真和假两种情况
  • Rust对于控制流的返回值类型也有约束 (比如: if要求所有分支返回相同类型), 但是在if内部中使用break, return, panic!()等方式结束时并不会要求相同类型, 这些表达式都属于特殊类型!, 且不受类型匹配的约束. fn exit(code: i32) -> !
  • !: 表示永远不会返回的类型, 而不是任意类型
fn wait(process: &mut Process) -> i32 {
    while true {
        if process.wait() {
            return process.exit_code();
        }
    } // 即使条件为true, Rust仍会假设false情况返回了(), 与返回类型不符
} // 但事实上该函数没有问题, 因为实际控制流只可能在return处返回i32值

6.9 函数与方法调用

特性: 成员访问运算符.会自动解析(多重)引用, 具体值或者智能指针

let x = gcd(2013, 432); // 函数调用

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

let mut numbers = Vec::new(); // 类型关联函数调用 (类似静态方法)

server.bind("127.0.0.1:3000").expect("error binding").run().expect("error running"); // 链式调用

6.10 字段与元素

student.name // 结构体字段

coords.1 // 元组元素

pieces[1] // 方括号用于访问数组, 切片, 向量的元素

6.11 引用运算符

// 成员访问运算符: 主要用于成员访问, 元素访问, 方法调用
// 解引用运算符: 主要用于引用所指向的值本身
fn main() {
    let padovan: Vec<u64> = vec![123, 321];
    for elem in &padovan {
        println!("{}", *elem); // 也可以省略
    }
}

6.12 算数运算符, 位运算符, 比较运算符, 逻辑运算符

仅描述比较特殊的约定:

  • 只有一元-, 没有一元+
  • 除零产生panic
  • Rust使用!表示按位非, 而不是~
    • 导致非零不能表示为!n而是应该写作n == 0
  • 移位总是对有符号进行符号扩展, 无符号进行零扩展
  • 与C不同, Rust位运算优先级高于比较运算
  • Rust比较运算符必须相同类型

6.13 赋值

  • Rust默认情况下变量不可变
  • Rust不支持链式赋值
  • Rust没有自增和自减运算符

6.14 类型转换

  • Rust类型转换需要显式转换as (比如: let index = x as usize;)
  • 数值 -> 其他内置类型:
    • 降低转换: 截断
    • 提升扩展: 符号扩展或者零扩展
    • 浮点->整数: 截断小数
  • bool, char, enum可以转换为整数, 但不允许反向转换
  • 引用的转换是非常宽松的
    • &String --自动转换-> &str
    • &Vec<i32> --自动转换-> &[i32]
    • &Box<Chessboard> --自动转换-> &Chessboard

6.15 闭包

闭包: 轻量级类似于函数的表达式 (其本质是表达式)

// 参数和返回值的类型自动推断, 也可以显式给出

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

// 如果指定了返回类型, 为了语法完整性, 主体必须是一个块

lei is_even = |x: u64| -> bool { x % 2 == 0 };

特色语法汇总

一些零碎的, 比较有rust特色的语法汇总

语法说明代码
breakloop联合loop本身是一个表达式, 可以生成值, 具体值由break返回
?运算符match对简写形式, 主要用于对OptionResult模式匹配let a = File::create(filename)?;
异常控制流与!类型虽然if要求所有分支返回相同类型值, 但是break, return, 无线loop, panic!, exit可以放宽类型检查fn exit(code: i32) -> !
比目鱼语法
在Rust中调用泛型方法不能直接使用类似于Vec<T>的语法, Rust会将其视为<运算符, 应该使用::<...>类似于比目鱼一样的语法.return Vec::<i32>::func()

更好的代码风格: 让Rust自行推断
return Vec::func()
..运算符start被包含, end则不被包含, 长度等于end - start

使用..=可以包含end

整个数组: ..
a到结尾: a ..
开头到b: .. b
a到b: a .. b
Rust没有自增和自减运算符
bool, char, enum可以转换为整数, 但不允许反向转换