本教程环境:
系统:MacOS
Rust 版本:1.77.2
Rust 的类型系统是它语言设计中最核心的部分之一。通过在编译时执行严格的类型检查来提供安全保障,帮助程序员避免常见的错误,例如空指针、解引用或类型不匹配等。以下是 Rust 类型系统的一些核心概念:
- 强类型 意味着每个值都必须具有一个确切的类型,而且这个类型是在编译时已经确定的。如果代码试图对两个不同类型的值进行操作(除非明确使用了类型转换),编译器将报错。
- 静态类型 类型检查在编译时进行,一切类型错误都会编译时捕。
同时,Rust 有以下两个特性可以让上述的这些工作变得轻松。
- 类型推断;
- 泛型。
下表是 Rust 类型的总结。
类型 | 说明 | 值 |
---|---|---|
i8 、i16 、i32 、i64 、i128 、u8 、u16 、u32 、u64 、u128 |
给定位宽的有符号整数和无符号整数 | 4 、-5i8 、0x400u16 、0o100i16 、20_922_789_888_000u64 、b'*' (u8 字节字面量) |
isize 、usize |
与机器字(32位或64位)一样大的有符号整数和无符号整数 | |
f32 、f64 |
单精度 IEEE 浮点数、双精度 IEEE 浮点数 | |
bool |
布尔值 | true 、false |
char |
Unicode 字符,32 位宽(4字节) | 'x' '\\n' |
(char, i8, bool) |
元组,允许混合类型 | |
() |
单元元组、空元组 | |
struct S { x: f32, y: f32 } |
具名字段型结构体 | |
struct T(i32, char) |
元组结构体 | |
struct E; |
单元结构体 | |
enum Attend { OnTime, Late(u32) } |
枚举 | |
Box<Attend> |
Box:指向堆中值的拥有型指针 | |
&i32 、&mut i32 |
共享引用和可变引用;非拥有型指针,其声明周期不能超出引用目标 | |
String |
UTF-8 字符串,动态分配大小 | |
&str |
对 str 的引用:指向 UTF-8 文本的非拥有型指针 |
|
[f64; 8] |
数组,固定长度,其元素类型都相同 | |
Vec<f64> |
向量,可变长度,其元素类型都相同 | vec![1, 2, 3, 4] |
&[u8] 、&mut [u8] |
对切片(数组或向量的某一部分)的引用,包含指针和长度 | &v[10..20] 、&mut a[..] |
Result<u64, Error> |
可能失败的操作结果:成功值 Ok(v) ;失败值 Err(e) |
|
Option<&str> |
可选值:没有值,取值为 None ;有值,取值为 Some(v) |
|
&dyn any 、&mut dyn Read |
特型对象:对任何实现了一组给定方法的值的引用 | value as &dyn Any 、&mut file as &mut dyn Read |
fn(&str) -> bool |
函数指针 | str::is_empty |
(闭包没有显式的书写形式) | 闭包 | | a, b | a * a + b * b |
整型
无符号整型 - u8
、u16
、u32
、u64
、u128
、isize
。u8
的取值范围 0 ~ 2^8 - 1
;其他的采用相同的方法计算。 有符号整型 - i8
、i16
、i32
、i64
、i128
、usize
。i8
的取值范围 -2^7 ~ 2^7 -1
; 其他的采用相同的方法计算。 isize
、usize
依赖与计算机架构,64 位架构是 64 位,32 位架构是 32 位的。 要获取整型类型的取值范围可以使用标准库中的 MIN
和 MAX
常量。
rust
// 返回 u8 类型的取值范围
println!("u8 range: {} ~ {}", u8::MIN, u8::MAX);
// u8 range: 0 ~ 255
u8
常作为**字节值。**例如,从文件或网路中读取的二进制数据是 u8
构成的流。 Rust 中数组索引是 usize
类型的值。表示数组或向量大小,或者某些数据结构中元素数量,通常也使用 usize
。
整型字面量
116i8
、0xcafu32
、0b0010_1010
、0o106
都是整型字面量。 前缀 0x
、0o
、0b
分别表示十六进制、八进制、二进制。
字节字面量
在 Rust 中,字节字面量 表示单个字节的值。可以用它们更加方便的表示 u8
类型的值。 字节字面量写作 b'x'
,x
可以是任何 ASCII 字符或转义字符。 例如,A 的 ASCII 码为 65,字面量 b'A'
和 65u8
完全等效。
rust
// 字节字面量
#[allow(non_snake_case)] // 去除警告
let A = b'A';
println!("A is {}", A); // A is 65
对于难以书写或阅读的字符,可将其编码改为十六进制。形如 b'\xHH'
,其中 HH
是任意两位十六进制数。可以将 ASCII 控制字符 escape
的字节字面量写成 b'\x1b'
,因为 escape
的 ASCII 码为 27,即十六进制的 1b
。
整型之间转换
在 Rust 中,整型之间不能进行隐式的转化,必须进行显式的转换。
- 使用
as
进行转换。超出取值范围时,转换会导致数据丢失。
rust
// 整型之间的转换
let a: i32 = 300;
let b: u8 = a as u8;
let c: i64 = a as i64;
println!("i32: {}", a); // 300
println!("u8: {}", b); // 44 转换为 u8 类型,只有值在 u8 的范围内才安全
println!("i64: {}", c); // 300
- 使用
try_from()
和try_into()
方法进行尝试,转换失败时,返回错误。try_from()
定义在std::convert::TryFrom
trait 中,尝试将一个类型转化为另一个类型。try_into()
定义在std::convert::TryInto
trait 中,尝试将一个类型转化为另一个类型。
rust
let d: i32 = 300;
// try_from
let f = u32::try_from(d).expect("数值超出了 u8 的范围");
println!("f: {}", f); // f: 300
let e = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("e: {}", e); // 数值超出了 u8 的范围: TryFromIntError(())
// try_into
let g: u32 = d.try_into().expect("数值超出了 u8 的范围");
println!("g: {}", g); // g: 300
let h = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("h: {}", h); // 数值超出了 u8 的范围: TryFromIntError(())
整型的一些常用方法
标准库提供了一些计算方法:
rust
assert_eq!(2_u16.pow(4), 16); // 求幂
assert_eq!((-4_i32).abs(), 4); // 求绝对值
assert_eq!(0b101101_u8.count_ones(), 4); // 求二进制1的个数"
整型溢出处理
在调试时,整型运算溢出时会出现 panic
。 发布构建中,运算溢出默认会回绕。 如果这种默认行为不是你想要的,可以使用一些其他方法。 主要分为如下四大类。
检查运算
返回结果的 Option
值。方法调用添加 checked_
前缀。
rust
// 10与20之和可以表示为u8
assert_eq!(10_u8.checked_add(20), Some(30));
// 很遗憾,100与200之和不能表示为u8
assert_eq!(100_u8.checked_add(200), None);
// 做加法。如果溢出,则会出现panic
let sum = x.checked_add(y).unwrap();
回绕运算
默认行为。 调用的方法添加 wrapping_
前缀。
饱和运算
饱和运算的 结果"紧贴着"该类型可表达的最大值和最小值 。调用的方法添加 saturating_
前缀。
溢出运算
会返回一个元组 (result, overflowed)
,其中 result
是函数的回绕版本所返回的内容,而 overflowed
是一个布尔值,指示是否发生过溢出。调用的方法添加 overflowing_
前缀。
浮点类型
Rust 提供了 IEEE 单精度浮点类型(f32
)和双精度浮点类型(f64
)。默认选择 f64
。 f32
类型和 f64
类型具有一些特殊值的关联常量:正无穷大(INFINITY
)、负无穷大(NEG_INFINITY
)、非数值(NAN
)、最小有限值(MIN
)、最大有限值(MAX
)。 std::f32::consts
模块和 std::f64::consts
模块提供了各种常用的数学常量,比如 E
、PI
和 2 的平方根。
类型转换
可使用 as
关键字转换。或 try_from
、try_into
、into
等。
rust
let f1: f64 = 1.03;
let f2: f32 = 1.03;
assert_eq!(f1 as f32, f2); // 通过
assert_eq!(f1, f2.into()); // assertion `left == right` failed
一些处理函数
round()
- 四舍五入到最接近的整数;ceil()
- 将上取整,得到不小于浮点数的最小整数。floor()
- 将下取整,得到不大于浮点数的最大整数。trunc()
- 仅保留整数部分。fract()
- 仅保留小数部分。
布尔类型 - bool
Rust 的布尔类型(bool
),具有两个值 true
、false
。 Rust 中不能将其他类型隐式的转换为布尔类型。所以,条件或循环语句中的条件必须是 bool
表达式。 as
运算符可将布尔类型转换为整型;反之不行。
字符 - char
字符是 char
类型,会以 32 位值(4字节)表示单个 Unicode
字符 。使用单引号创建 char
字面量。
rust
// char
#[allow(unused_variables)]
fn main() {
let letter: char = 'a'; // 英文字母
let number: char = '1'; // 数字字符
let symbol: char = '$'; // 符号
let space: char = ' '; // 空格
let emoji: char = '😂'; // 表情符号
let chinese: char = '中'; // 中文字符
// 一些 char 类型的操作
println!("{} is alphabetic: {}", letter, letter.is_alphabetic()); // a is alphabetic: true
println!("{} is digit: {}", number, number.is_digit(10)); // 1 is digit: true
println!("{} to uppercase: {}", letter, letter.to_uppercase()); // a to uppercase: A
}
可以使用 as
将 char
转换为整型;如果转换的数值类型小于 32 位,高位会截断。 u8
是唯一能通过 as
运算符转换为 char
的类型。 标准库函数 std::char::from_u32
可以接受任何 u32
值并返回一个 Option<char>
:如果此 u32
不是允许的 Unicode
码点,那么 from_u32
就会返回 None
,否则,它会返回 Some(c)
,其中 c
是转换成 char
后的结果。
元组
元组是各种类型的值对,例如二元组、三元组等等。所以也叫做 n 元组。不同长度的元组类型不同。 元组的每个元素类型可不同。只允许通过常量下标来获取 ,例如元组 a
,要获取第三个元素需要通过 a.2
获取。 元组通常用来从一个函数返回多个值。 例如,字符串切片的 split_at
方法将字符串分成两半,并返回一个元组。可以使用模式匹配的方法将返回值的每个元素赋值给不同的变量。
rust
// 元组
#[allow(unused_variables)]
fn main() {
let temp = (); // 零元组
let one = (100, );
let two = (100, 200);
println!("{}", two.0); // 100
let text = "Hello World";
// 元组解构
let (hello, world) = text.split_at(5);
println!("hello: {}, world: {}", hello, world); // hello: Hello, world: World
}
元组类型的零元组是 ()
。不返回值的函数的返回类型是 ()
。 如果元组只有一个元素,那么必须在后面添加逗号,例如 (100, )
。
指针类型
Rust 有多种表示内存地址的类型。
引用
&String
是对 String
值的引用。&i32
是对 i32
值的引用。以此类推。 表达式 &x
会生成一个对 x
的引用。给定一个引用 r
,表达式 *r
会引用 r
指向的值。 Rust 的引用有两种形式:
&T
不可变共享引用。只读。可以同时拥有多个给定值的共享引用。&mut T
可变的独占引用。可以读取和修改它指向的值。如果该引用存在,就不能对该值有任何其他类型的引用。
rust
// 引用
fn main() {
let mut a = 10;
let b = &a;
println!("b: {}", b); // 10
println!("a: {}", a); // 10
let c = &mut a;
*c = 11;
println!("c: {}", a); // 11
}
智能指针
智能指针是一个数据结构,它除了提供对数据的访问之外,还包含了额外的元数据和功能。在 Rust 中,智能指针通常通过实现 Deref
和 Drop
trait 来提供这些额外的能力。智能指针的核心特性是它能自动管理内存,确保代码安全和高效地运行。 Rust 标准库中几种常用的智能指针如下。
Box<T>
一个在堆上分配内存的智能指针。它拥有它指向的数据,在离开作用域时自动清理。Rc<T>
一个引用计数的智能指针,允许数据有多个所有者。它主要用于单线程场景下的数据共享。Arc<T>
是Rc<T>
的线程安全版本,适用于多线程场景。Cell<T>
和RefCell<T>
提供内部可变性,即使在拥有不可变引用的情况下也可以改变所包含的值。
裸指针
- 不可变原始指针 (
*const T
): 可以多次复制,并且可以并发读取指向的数据,但无法保证指向的内存是有效的。 - 可变原始指针 (
*mut T
): 它提供了一个可改变的内存地址,不过使用这种类型需要特别注意,因为存在潜在的数据竞争问题。
原始指针不在 Rust 的安全抽象之内,使用它们需要 unsafe
代码块,因为编译器无法保证其安全性。它们通常用于与 C 代码的交互,或者执行一些底层的内存操作。
数组、向量、切片
数组
数组的类型为 [T; N]
,每个值都是 T
类型,数组的长度是 N
。数组是编译期确定的常量。数组的长度是类型的一部分。数组不能追加新元素或缩小。 编写数组的方法:
rust
let a: [i32; 3] = [1, 2, 3];
let b = ["a", "b", "c"];
a[0] // 1
a.len() // 3
数组本身没有提供诸如遍历、搜索、排序等方法,这些都是切片提供的方法。 但是在写的时候,数组可以调用这些方法,是因为 Rust 会隐式的创建一个引用整个数组的切片,然后使用这个切片进行这些操作。例如:
rust
let mut chaos = [3, 5, 4, 1, 2];
chaos.sort();
assert_eq!(chaos, [1, 2, 3, 4, 5]);
向量
Vec<T>
类型可称为 T
的向量。是一个动态分配且可增长的 T
类型的值序列。向量的元素存在于堆中,所以可以随意调整向量的大小。 向量有一下几个关键信息:
- 有一个指向堆上数组开始位置的指针;
- 向量的长度,即当前向量持有的元素数量;
- 向量的容量,即堆上分配的内存能容纳的元素数量。
当堆上的缓冲区达到其最大容量时,往向量中添加另一个元素需要分配一个更大的缓冲区,将当前内容复制到其中,更新向量的指针和容量以指向新缓冲区,最后释放旧缓冲区。 创建向量方式很多。最简单使用 vec!
宏来创建向量。
rust
let v = vec![2, 3, 5, 7];
v.push(8); // 添加元素
let repeat_v = vec![0; 4] // [0, 0, 0, 0]
let vec_v = Vec::new(); // 创建空向量。
vec_v.push('a');
// 从迭代器生成的值构建一个向量
let v: Vec<i32> = (0..5).collect(); // 使用 collect 时候,通常要指定类型
和数组一样,可以对向量使用切片的方法。
rust
let mut v = vec![1, 2, 3, 4];
v.reverse();
如果事先知道向量所需的元素数量,可使用 Vec::with_capacity
来创建。这样创建的缓冲区足够大,一开始就可容纳所有元素。vec!
宏就使用了这个技巧。 len()
方法返回向量包含的元素数。 capacity()
方法返回不重新分配下的课容纳的元素数量。 可以在向量的任意位置删除或插入元素。这个操作会影响之后的元素,它们需要向前或向后移动。所以,如果向量越长,操作越慢。
rust
let mut v = vec!(1, 2, 3, 5, 6);
v.insert(3, 4); // 插入元素
v.remove(1); // 删除索引为1的元素
v.pop(); // 移除最后一个元素并返回
for item in v {
println!("{}", item);
}
切片
切片([T]
),是数组或向量中的一个区域。 类型 &[T]
和 &mut [T]
可称为 T
的共享切片 和 T
的可变切片 。切片是对数组或向量的一部分值的引用。 对切片的引用是一个胖指针:一个双字值,包括指向切片第一个元素的指针和切片中元素的数量。
rust
let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707];
let sv: &[f64] = &v;
let sa: &[f64] = &a;
普通引用是指向单个值的非拥有型指针 。对切片的引用是指向内存中一系列连续值的非拥有型指针。 如果要对数组或向量进行操作,使用切片是不错的选择。
rust
fn print(n: &[f64]) {
for elt in n {
println!("{}", elt);
}
}
可以使用范围值对数组或向量进行索引,来获取一个切片的引用。
rust
print(&v[0..2]);
print(&v[2..]);
print(&sv[1..3]);
字符串
str
在 Rust 中 str
是不定长字符串切片类型,它是一种不拥有所有权的类型。不能直接声明。因为 Rust 在编译期间需要确定每个类型的长度。 所以一般使用的是字符串切片的引用 &str
。它是不可变的。
字符串字面量
用双引号包裹,如果里面有 "
用反斜杠转译。它的类型就是 &str
。
rust
let speech = "\"Ouch!\" said the well.";
Rust 提供了**原始字符串。**用小写字母 r 进行标记。原始字符串中所有反斜杠和空白字符都会逐字包含在字符串中。
rust
let default_win_install_path = r"C:\Parogram Files\Gorillas";
字节串
带有 b
前缀的字符串字面量都是**字节串。**这样的字符串是 u8
值(字节)的切片而不是 Unicode 文本。
rust
let method = b"GET";
method
的类型是 &[u8; 3]
,它是对 3 字节数组的引用。 原始字节串 要以 br"
开头。
String
在 Rust 中,String 类型是一个标准库提供的动态、可增长、可变的、拥有所有权的 UTF-8 编码的字符串类型。它是用来存储和操作可变长的文本数据的。 String
类似 Vec<T>
。它会在堆上分配自己的缓冲区。 String
创建的几个方法:
.to_string()
将&str
转换为String
。会进行复制。format!()
宏会返回一个新的String
。- 字符串的数组、切片和向量都有
concat()
和join()
方法,能从多个字符串中形成一个新的String
。
rust
let mut s = String::new(); // 空的 String
let hello = String::from("Hello, world!"); // 从字符串字面量创建 String
let mut s = String::from("foo");
s.push_str("bar"); // 追加字符串切片
s.push('!'); // 追加单个字符
println!("{}", s); // 输出:"foobar!"
let s = String::from("example");
let slice: &str = &s; // 借用 String 为 &str
let slice = "example";
let s = slice.to_string(); // 或 String::from(slice)
其他类似字符串的类型
- 对于 Unicode 文本,坚持使用
String
和&str
。 - 使用文件名时,改用
std::path::PathBuf
和&Path
。 - 当处理根本不是 UTF-8 编码的二进制数据时,请使用
Vec<u8>
和&[u8]
。 - 当使用操作系统提供的原生形式的环境变量名和命令行参数时,使用
OsString
和&OsStr
。 - 当和使用
null
结尾字符串的 C 语言库进行互操作时,请使用std::ffi::CString
和&CStr
。
类型别名
使用 type
关键字来为现有类型声明一个新名称:
rust
type Bytes = Vec<u8>;
本教程代码仓库:github.com/zcfsmile/Ru...
参考链接:
🌟🌟 🙏🙏感谢您的阅读,如果对你有帮助,欢迎关注、点赞 🌟🌟