【Fitz】Rust 基本类型
说明:本文主要是Rust语言圣经相关章节的学习笔记,大部分与其内容相同,欢迎阅读原文。
Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。基本类型意味着其往往是一个最小化原子类型,无法解构为其他类型,由以下类型组成:
- 数值类型: 有符号整数 (
i8
,i16
,i32
,i64
,i128
,isize
)、 无符号整数 (u8
,u16
,u32
,u64
,u128
,usize
) 、浮点数 (f32
,f64
)、以及有理数、复数 - 字符串:字符串字面量和字符串切片
&str
- 布尔类型:
true
和false
- 字符类型: 表示单个 Unicode 字符,存储为 4 个字节
- 单元类型: 即
()
,其唯一的值也是()
Rust 编译器会根据变量的值和上下文中的使用方式来自动推导出变量的类型,但有时无法推导出类型需要手动给予一个类型标注。
数值类型
整数和浮点数
Rust 使用相对传统的语法来创建整数(1,2,...)和浮点数(1.0,2.0,...),并通过常见运算符完成运算。Rust 允许运算符重载,即在复杂类型上定义运算符。
整数类型
Rust 内置的整数类型在前面的有符号整数和无符号整数中给出了,类型定义的形式统一为 有无符号+类型大小(位数)
。有符号数字以补码形式存储。
整型字面量可以用十进制、十六进制 0x
、八进制 0o
、二进制 0b
和字节(仅限于 u8
)b'A'
的形式来书写。
Rust 整型默认 i32
,isize
和 usize
主要应用场景是用做集合的索引。
当发生整数溢出时,在 debug
模式下会 panic(遇到错误程序停止执行),在 release
模式下会按照补码循环溢出的规则处理,简言之就是大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值,如在 u8
类型下257会变成1,但程序不会 panic。
要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:
- 使用
wrapping_*
方法在所有模式下都按照补码循环溢出规则处理,例如wrapping_add
- 如果使用
checked_*
方法时发生溢出,则返回None
值 - 使用
overflowing_*
方法返回该值和一个指示是否存在溢出的布尔值 - 使用
saturating_*
方法使值达到最小值或最大值
浮点类型
Rust 中浮点类型有两种基本类型:f32
和 f64
。默认浮点类型是f64
,现代CPU中二者速度几乎相同但 f64
精度更高。浮点数根据 IEEE-754
标准实现。
数字运算
Rust 支持所有数字类型的基本数学运算:加法、减法、乘法、除法和取模运算。
浮点数陷阱
浮点数在使用时不够谨慎就可能造成危险:
- 浮点数往往是想要数字的近似表达;
- 浮点数在某些特征上是反直觉的,如浮点数使用的
>
、>=
等比较符号,实际上是用std::cmp::PartialEq
trait(类似于其他语言的接口)实现的,但是并没有实现std::cmp::Eq
trait,也就是不能判断浮点数是否相等。例如就不能将其作为 HashMap 的 Key 来使用。
为了避免上面说的两个陷阱,你需要遵守以下准则:
- 避免在浮点数上测试相等性
- 当结果在数学上可能存在未定义时,需要格外的小心
例如对于 0.1 + 0.2 == 0.3
类似的比较,程序会 panic,如果必须要比较可以考虑使用 (0.1_f64 + 0.2 - 0.3).abs() < 0.00001
这种方式来实现。
NaN
Rust 的浮点数类型使用 NaN
(Not a Number)来处理数学上未定义的结果。所有跟 NaN
交互的操作,都会返回一个 NaN
,且 NaN
不能用来比较。出于防御性编程的考虑,可以使用 is_nan()
等方法,来判断一个数值是否是 NaN
。
序列(Range)
Rust 使用 ..
符号来生成连续的数值,使用 ..=
来包括右边界值。序列只允许用于数字或字符类型,因为其可以连续,同时编译器在编译期可以检查该序列是否为空。
使用示例如:0..10
或 'a'..='z'
。
有理数和复数
Rust 语言没有将有理数和复数包含在标准库中:
- 有理数和复数
- 任意大小的整数和任意精度的浮点数
- 固定精度的十进制小数,常用于货币相关的场景
但是社区已经开发出了高质量的 Rust 数值库:num。
在 Rust 中,类型转换必须是显式的,Rust 的数值上可以使用方法,例如可以用 13.14_f32.round()
对 i32
类型的数进行取整。
字符、布尔、单元类型
字符类型(char)
Rust 中的 char
类型是 Unicode 标量值。Unicode 值的范围在 U+0000 ~ U+D7FF
和 U+E000 ~ U+10FFFF
之间,Unicode 都是4个字节编码,因此字符类型也是占用4个字节。
Rust 中字符只能用 ''
来表示,""
是留给字符串用的。
布尔(bool)
Rust 中的布尔类型有两个可能的值:true
和 false
,布尔值占用1个字节的内存大小。常用于流程控制。
单元类型
单元类型就是 ()
,唯一的值也是 ()
。
main函数返回的就是单元类型,没有返回值的函数在 Rust 中是有单独定义的:发散函数
,即无法收敛的函数。常见的 println!()
的返回值也是单元类型 ()
。
语句与表达式
Rust 是基于表达式的语言,但也有语句。语句会执行一些操作但是不会返回值,表达式会在求值后返回一个值。Rust 的函数体是由一系列语句组成,最后由一个表达式来返回值。
对于 Rust 而言,这种基于语句和表达式的方式是非常重要的,需要能明确区分这两个概念。基于表达式是函数式语言的重要特征,表达式总要返回值。
语句
语句完成了一个具体的操作,但是没有返回值,比如最常见的 let
语句。如果发生了这种类型的错误,编译器会给出错误提示:error: expected expression, found statement
。
表达式
表达式会运算求值,然后返回一个值。表达式可以成为语句的一部分。调用给一个函数是表达式,因为会返回一个值,调用宏也是表达式,用花括号包裹最终返回一个值的语句块也是表达式,总之,能返回值的就是表达式。
表达式不能包含分号,一旦在表达式后加上分号,就会变成一条语句,也就不会返回一个值。
函数
函数要点
- 函数名和变量名使用 蛇形命名法(snake case),例如
fn add_two() -> {}
- 函数的位置可以随意放,Rust 不关心在哪里定义了函数,只要有定义即可
- 每个函数参数都要标注类型
函数参数
Rust 是强类型语言,需要为每一个函数参数都标识出它的具体类型。
函数返回
在 Rust 中函数就是表达式,因此可以把函数的返回值直接赋给调用者。
函数的返回值就是函数体最后一条表达式的返回值,也可以使用 return
提前返回。
Rust 中的特殊返回类型
无返回值 ()
单元类型 ()
是一个零长度的元组,可以用来表达一个函数没有返回值:
- 函数没有返回值,那么返回一个
()
- 通过
;
结尾的表达式返回一个()
永不返回的函数 !
当用 !
作为函数返回类型时,表示该函数永不返回,这种语法往往用做会导致程序崩溃的函数。