Skip to content

Rust 基础

参考资料

  • Rust 程序设计 - 神书, 够深入 - 5

安装环境

  • Rustup 一键式解决, 最简单一集

系统程序员也能享受美好

  • 吹一堆 nb
  • Rust 不仅是实现了零成本的垃圾回收, 同时包括零成本的并行内存安全

我的观点

  • 任何一种编程语言, 只要是图灵完备的, 就能描述世界上所有的代码
  • Rust 追求的理念, 无非就是将代码限制于一个较小的子集
  • 恰好能够保证通过静态分析的方法就能实现内存安全的同时, 能够拥有足够强的灵活性与表达力
  • 除此之外, 他还融入了大量已经广泛使用的零成本抽象

Rust 导览

  • Rust 确实拥有非常优秀的一个工具链
  • cargo 提供了一些规范性的项目结构, 这往往我们认为是框架当中应该提供的
  • 以及非常优秀的包管理, 非常优秀的构建, 在一定程度上可以代替 make
  • 一个 Rust 包, 称为一个 crate
[package]
name = "hello_world"
version = "0.1.0"
edition = "2018"

[dependencies]
... = "1.0.0"

函数

fn add(mut a:i32, b:i32) -> i32 {
    println!("Hello, world!");
}
  • mut 表示可变参数, 默认为不可变
  • assert!() 表示断言, 用于调试, 其中, ! 表示为宏调用
{
    ...;
    m // 任意以 {} 包住的代码块都可以用作表达式,
      // 若以没有尾随着分号的表达式结尾, 则这个表达式的值就是整个表达式的值
      // 如果没有这个结尾无分号表达式的话, 则这个表达式的值就是 "()"
}
  • Rust 存在 return , 适用于在函数中提前返回

属性

  • Rust 属性, 类似 Jave 注解
#[test] // debug 时运行, 发布时忽略
fn test_add() -> () {
    assert_eq!(add(1, 2), 3);
}

特型

  • 类似接口, 任意类实现了这个特型, 就可以使用这个特型的方法
  • 使用 use 关键字引入特型与模块

ResultOption

  • 两个枚举类型
  • Result 表示成功或失败, 成功则返回 Ok(T), 失败则返回 Err(E)
  • Option 表示可选值, 有值则返回 Some(T), 无值则返回 None

基本数据类型

  • Rust 拥有简单的类型推断和泛型函数, 这种泛型也是零成本抽象
  • Rust 几乎不会进行隐式类型转换, 可以使用 num as type 去进行显示转换

固定宽度的数值类型

  • u8-u128, i8-i128, f32-f64, 显而易见, 非常爽
  • usize 与 isize 表示与当前机器字等宽的类型
  • 在字面值当中可以指定类型, 如果没有指定, 会进行类型推断, 有多种候选类型, 默认使用 i32, 无法认定则报错
  • 0x, 0o, 0b 作为进制前缀

操作

  • (-4).abs() 是错误的, 因为无法进行任何类型推断
  • -4i32.abs() 可能与你的预期不符, 方法的优先级大于一元运算符
  • (-4i32).abs()i32::abs(-4) 可能达到你的预期

溢出

  • 当你进行调试构建的时候, 整数运算溢出会产生一个 panic 或者说 'error'
  • 但在发布构建当中, 会发生回绕, 也就是说正确的结果对类型范围的取模
  • 整型提供方法去禁止这些行为
    • checked_ 方法, 结果正确返回 Some(result), 结果不正确则为 None
    • wrapping_ 方法, 结果正确返回 result, 结果不正确则回绕
    • saturating_ 方法, 结果正确返回 result, 结果不正确则返回边界值
    • overflowing_ 方法, 结果正确返回 (result, false), 结果不正确则返回 (result, true)
      • 注意不存在饱和除法, 求余法和位移法
  • 这些前缀支持以下几种运算后缀
    • add, sub, mul, div, rem, shl, shr
    • neg (取反), abs, pow

浮点类型

  • f32/f63 精度至少为 6/15 位
  • 浮点数的字面量类型行为与整型差不多, 默认类型是 f64
  • 特别的, 字面量包含 MIN, MAX, NEG_INFINITY, INFINITY, NAN
  • 浮点数提供一些方法, 比如 sqrt
  • std::f32::consts 模块提供了一些常量, 比如 PI, E

布尔类型

  • 只有两个值, truefalse

字符类型

  • 字符类型与整型截然不同
  • 字符类型是 char, 占 4 字节, 可以表示任何 Unicode 字符
  • 转义字符是有效的, 如'\n', '\xHH' (HH 是任意两位十六进制数)
  • 特别的, 可以使用 b'c' 去表示一个 u8 字面量 (仅限 ASCII 码)
    • u8 类型可以使用 as 转换为 char
    • char 类型可以使用 as 转换为 整型 (可能截断)
  • std::char::from_u32 方法可以将 u32 转换为 char
    • 其返回值是 Option<char> (Some(char) 或者 None)
  • string 是 UTF-8 字节序列而非 char 数组

元组类型

  • 元组类型类似与结构体, 其类型表达为 (T1, T2, ..., Tn)
  • 元组类型可以使用 let 进行解构, 如 let (x, y) = (1, 2)
  • 对于一个元组对象 name.0 可以访问其第 0 个元素, 以此类推
    • name.i 是不合法的
  • () 表示一个空元组, 其类型为 (), 类似于 void

指针类型

  • Rust 旨在将内存分配保持在最低限度, 如 ((10, 20), (30, 40)) 等效于 (10, 20, 30, 40)
  • Rust 提供了 & 引用类型, 其行为类似指针, 称 &x 是借用了对 x 的引用
  • Rust 有两种引用, 它们是互斥的, 只能二选一, 分别是共用引用和可变引用
    • &x 是共用引用, 可以被多个引用同时拥有
    • &mut x 是可变引用, 只能有一个引用拥有
  • let a = Box::new(10); 可以创建一个堆上的对象, 其类型为 Box<T>
    • Box<T> 是一个智能指针, 其行为类似指针, 但其可以自动释放
    • 除非 Box<T> 被移动 (move)
  • 对于裸指针 *const T*mut T, 解引用时需要使用 unsafe

数组, 向量和切片

  • 数组是固定长度的序列, 其类型为 [T; n], 其中 n 是一个常量表达式
  • 向量是可变长度的序列, 其类型为 Vec<T>, 其元素存在于堆上
  • 切片是对一系列值的引用, 其类型当然也包括 &mut [T]&[T]
  • 切片从数组或向量的某一点开始, 一直到它的结束
let a = [1, 2, 3, 4, 5]; // 初始化一个数组
let b = [0u8; 10]; // 初始化一个长度为 10 的数组, 所有元素为 0

for i in 0..a.len() { // 遍历数组
    a[i] = i as u8;
}

if a[0] == 0 { // 访问数组元素
    a[0] = 1;
}

```rust
let mut v = Vec::new(); // 初始化一个向量
let mut v = vec![1, 2, 3]; // 初始化一个向量, 并添加元素
let mut v = vec![0u8; 10]; // 初始化一个长度为 10 的向量, 所有元素为 0
let v: Vec<u8> = (0..10).collect(); // 初始化一个向量, collect() 表示将元组迭代器的所有元素收集到一个向量中
let mut v = Vec::with_capacity(10); // 初始化一个容量为 10 的向量

v.push(1); // 向向量中添加元素
v.pop(); // 弹出向量的最后一个元素

v.len(); // 获取向量的长度
v.capacity(); // 获取向量的容量

v.is_empty(); // 判断向量是否为空
v.clear(); // 清空向量

v.insert(0, 1); // 插入元素到向量的第 0 个位置
v.remove(0); // 移除向量的第 0 个元素
  • 向量本质包含一个指针, 一个长度, 一个容量
  • 当向量的长度超过容量时, 向量会销毁缓冲区, 并重新分配一个更大的缓冲区
  • Vec 不是内置类型

字符串

  • Rust 提供了两种字符串类型, 分别是 String&str
let s = "Hello, world!"; // 初始化一个字符串
let s = "Hello, w \ // 注意先导空格
    orld!" // 等效
let s = "Hello, 
    world!" // 会保留换行符

let s = r#"..."# // 原始字符串, 不会转义
// 任意数量的 # 标识边界, 以在原始字符串当中使用 " 和 #

// 字节串
let s = b"Hello, world!"; // &[u8; n] 类型
  • 内存中使用 UTF-8 编码表示字符串, 其单字符的长度可以是 1 到 4 字节
  • String 类型类似 Vec<u8>, &str 类型类似 &[u8]
  • &str 对于预分配文本行为类似 C 中的字符串字面量
let s = "Hello, world!".to_string(); // 将字符串字面量转换为 String
let s = format!("Hello, {}!", "world"); // 将字符串字面量转换为 String

// ss 为字符串的数组, 向量, 切片
let s = ss.join(", "); // 将字符串数组, 向量, 切片连接成一个字符串
let s = ss.concat(); // 将字符串数组, 向量, 切片连接成一个字符串
  • 字符串可以使用 ==, !=, <, <=, >, >= 进行比较
  • 但其行为因为 Unicode 的性质并不总符合你的预期
  • 特别的, Rust 为 C 字符串 / OsString / 文件名提供了一些专用的类型

类型别名

  • type name = Vec<u8> 可以定义类型别名

所有权与移动