第 3 章 基本类型

Rust 的类型服务于 3 个目的:安全(检查类型,防止错误),效率(对内存进行不同颗粒度的控制),简介。Rust 采用事先编译的方法,并且是静态类型的语言,不需要实际运行就可以检查所有可能的运行路径。
Rust 要求在写代码的时候进行规划,要写出函数的参数、返回值的类型、结构体成员的类型。Rust 提供了推断和泛型两个特性来辅助我们,前者能根据上下文进行类型推断而不需要显式地写出,后者允许通用的函数目的和实现能够适用于任何符合标准的集合。
以下是 rust 中的类型

机器类型

整数类型

Rust 中的整数类型有以下几个特点

  1. Rust 中要求数值类型必须写出位宽以及其表现形式,比如 i32, u16 等等,比较特殊的是 isize, usize 与机器上内存地址的宽度相同。
  2. Rust 将字符和数值类型区别对待,char 既不是 u8 也不是 i8,这点与 C, C++ 均不同。
  3. Rust 要求数组索引必须是 usize 值。
  4. Rust 会在调试构建时检查算术运算中是否有整数溢出,如果有会抛出诧异。如果一定要使用整数溢出,需要使用类似 x.wrapping_add(y) 这样的写法。
  5. Rust 中的整数字面量可以加上后缀来指定类型,比如 42u8 是一个 u8 值。若不包含后缀,Rust 会根据上下文推断出最合适的。当有若个可能的推断时,对于某些情况 Rust 会有默认行为,如若可能的类型包括 i32 那么 Rust 会默认其为 i32,否则会报错(歧义)。
  6. Rust 提供字节字面量,即用类字符字面量表示的 u8 值,如 b'X' 表示一个值为 X 的 ASCII 编码的 u8 值。这里只能是 ASCII 字符,下表展示了几个需要转义的字符。对于难写或难读的字符,还可以使用 b'xHH' 形式,其中 HH 是 16 进制的。
  7. 使用 as 操作符来进行整数类型转换。
  8. Rust 对整数也提供了很多方法,具体可以参考 Rust 文档。

浮点类型

Rust 支持 IEEE 标准的单双精度浮点类型。浮点字面量由整数部分、小数部分、指数和类型后缀组成,除了整数部分均是可选的,但后三者必须有其中之一(否则无法与整数字面量区分)。小数部分可以仅有小数点,如 5. 是一个合法的浮点常量。
Rust 会对没有类型后缀的浮点数推断类型,如果两种均有可能则默认为 f64。Rust 将整数字面量与浮点字面量当作两种不同的类型,两者不会互相推断。
std::f32, std::f64 两个标准库中定义了 IEEE 要求的特殊值,比如 INFINITY, NEG_INFINITY, NAN, MIN, MAX,模块 std::f32::consts, std::f64::consts 给i共了很多有用的数学常数。
Rust 对浮点类型提供了若干方法,具体可以参考 Rust 文档。

不论是整数类型还是浮点类型,Rust 不会作任何形式的隐式转换,诸如将 i32 数传入 f64 为参数的函数、甚至 i16 为参数的函数这些行为都是会导致错误的。

布尔类型

Rust 的布尔类型 bool 提供 true 和 false 两个值。但 Rust 不允许将字符、整数、浮点或指针隐式地转化成 bool,控制表达式的条件以及逻辑运算符的操作数都要求必须是 bool 表达式。
as 操作可以将 bool 转化成整数类型,但反过来不成立。

字符类型

Rust 中字符类型 char 以 32 位值的方式表示单个 Unicode 字符,同时对字符串或文本流使用 UTF-8 编码。String 将其文本表示为一个 UTF-8 字节的序列。
Rust 只支持在其 char 中保存符合 Unicode 标准的字符,Rust 会使用类型系统以及动态检查来保证 char 的值始终在许可的范围内。
char 类型不会与整数类型进行隐式转换,但可以用 as 显示转换;另一方面 u8 是唯一能通过 as 转换到 char 的整数值,如果需要转换 u32 的话需要使用标准库函数 std::char::from_u32u32 转换为 Option<char> 值。
标准库中为字符提供了若干方法,具体可以参考 Rust 文档。

元组

元组由若干不同类型的值组成,其写法为类似 let t = (a, b, c),然后使用 t.0, t.1 这样的方式访问其中的元素。

与数组不同的地方:
每个元素可以是不同的类型
只能使用常量作为索引,所以比如 t.it[i] 这样的写法是不允许的。

元组可以认为是一种极度简化的结构体类型,更利于表达合适的程序逻辑。
当不存在有意义的值而上下文要求某种类型时,Rust 会返回零元组 ()
在元组的最后一个括号增加逗号不影响元组的解析。

指针类型

Rust 包括三种指针类型:引用、Box 和不安全指针。

引用: &String 类型的值就是一个对 String 值的引用,引用可以指向任何地方的任何值,可以是栈上或者堆上。&x 表示产生一个对 x 的引用,或用 Rust 语言来表述是借用了一个对 x 的引用。而对于一个引用 r,用 *r 表示 r 指向的值。
与 C 语言中不同的是,Rust 中引用永远不会是空值,在安全的 Rust 代码中根本不会产生空引用,且 Rust 引用默认不可修改。如果需要修改,要使用 &mut T 来声明。

Box:是 Rust 在堆上分配一个值的最简单方式,如 Box::new(t);。后续会与转移配合解释这方面的内容。

不安全指针:形式为 *mut T*const T,实际上就是 C 和 C++ 中的指针。这类指针的使用是不安全的,仅能在 unsafe 块中使用。

数组、向量和切片

Rust 使用这三种形式在内存中表示一系列值。

  • 类型 [T; N] 表示一个长度为 N 的、每个值类型为 T 的数组。数组的长度是固定的,是数组类型的一部分,在编译的时候就已经确定了,这意味着若 n 是一个变量,[T; n] 这样的定义是不合法的。最常见的方式是将所有的值用逗号相隔开,再用方括号括起来,如 [0,1,2,3,4]。另一种方式是 [V; N] ,表示一个每个值为 V 的长度为 N 的数组。Rust 没有语法用来表示未初始化的数组。
  • 类型 Vec<T> 表示 T 的向量。这是一种可以动态分配、扩展的值序列,其中的元素保存在堆上。创建向量的方式有若干种,其中最简单的是使用 vec! 宏,语法类似创建数组字面量 vec![0, 1, 2]。这一宏定义的方法等价于先调用 Vec::new() 创建一个新的空向量,然后使用 push 将元素推入。也可以使用迭代器的方式创建向量,如 let v: Vec<i32> = (0..5).collect(); ,这个例子等价于 [0,1,2,3,4]。一个 Vec<T> 包含三个值:对分配在堆上用于保存元素的缓冲区的引用、该缓冲区可以存储的元素个数、当前实际保存的元素个数。当提前知道向量中需要保存的元素个数时,可以使用 Vec::with_capacity() 来创建一个有足够缓冲区的向量。由于 Rust 会尝试将长度向上对齐,所以这里分配的空间可能超过输入的值。
  • 类型 &[T], &mut [T] 是 T 的共享切片核 T 的可修改切片,是对其它值(通常是数组或向量)种部分元素的引用。切片的引用是一个胖指针,即是一个双子宽的值,包括一个指向切片第一个元素的指针和切片中元素的个数。很多数组和向量上的方法(比如 sort, len, reverse)实际上是定义在切片上的,Rust 编译器会自动隐式的将整个数组或向量作为切片来进行操作。

字符串

字符串字面值,与 char 字面值类似,但单引号无需转义而双引号需要转义。字符串字面值可以分散到多行,如果一行的末尾没有以反斜杠结尾,那么该行的换行符和下一行前面的空白字符都会包括在内,否则就会被删除。为避免过多的转义带来的麻烦,Rust 还提供了原始字符串的语法,,以小写字母 r 开头,然后用双引号括起来,中间的字符串的反斜杠和空白字符都会原样保留,转义规则无效。在原始字符串的开头和结尾加上相同长度的若干 # 来辅助规定字符串的开头核结尾。
字节字符串,即带有前缀 b 的字符串字面量。注意字节字符串是 u8 值的切片,不是 Unicode 文本的切片,不支持任意 Unicode 字符,只能是 ASCII 和 \xHH 转义序列。
Rust 字符串,是 Unicode 字符序列,但在内存中并不是以 char 数组的形式存储的,而是使用 UTF-8 可变宽度编码存储。Rust 对 String 的具体实现是 Vec<u8>&str 是对其它变量拥有的一串 UTF-8 的文本的引用,可以看作是 &[u8]。对于 String&strlen() 方法返回的是字节的长度而非 UTF-8 字符的数量。
有多种方法创建 String。首先是 &strto_string() 方法,实际上是复制字符串。其次有类似 println! 类似的 format! 宏用于创建格式化的字符串。最后有 String 的切片上的方法 concatjoin 将多个字符串拼接为新的 String。
String 的 ==!= 仅比较字符串中包含的字符,不关心是否指向内存中的同一地址。