Rust 学习笔记
这是一份系统的 Rust 学习笔记,涵盖了从基础语法到高级特性的内容。
📋 目录
- 环境配置
- 基础语法
- 所有权系统
- 复合类型
- 控制流
- 模式匹配
- 面向对象
- 泛型与特征
- 集合类型
- 生命周期
- 错误处理
- 模块系统
- 高级特性
- 消息传递
- 互斥锁
- 错误处理进阶
- 宏系统
- 异步编程
- 测试
- 工作空间
- 解引用
- Unsafe Rust
- 类型别名
环境配置
镜像设置
# 安装 crm 来设置镜像
cargo install crm
# 测试最快镜像
crm test
# 自动设置最快镜像
crm best
基础语法
1. 输出与格式化
// 使用 {} 自动占位
println!("{}", a);
// "" 代表字符串,'' 代表字符
2. 文档注释
/// 文档注释
/// # Examples
//! 为包(crates)或者模块添加文档注释
// 配合 cargo doc --open 运行并预览
3. 表达式与返回值
- 隐式表达式不能包含分号:
x + y - 如果显式的 return 则无所谓
- 函数没有返回值,默认返回
() - 表达式以分号
;结尾,比如x + y;,返回()
4. 数据类型
| 类型 | 描述 | 范围 |
|---|---|---|
u32 | 无符号整型,32位 | 只有正数 |
i32 | 有符号整型,32位 | -(2^n - 1) ~ 2^(n-1) - 1 |
isize/usize | 由计算机决定 | - |
f32 | 浮点类型 | 默认 f64,精度更高 |
5. 永不返回类型
// 用感叹号 ! 作为返回类型,表示该函数永不返回(diverging functions)
fn dead_fn() -> ! {
panic!("你已经到了穷途末路,崩溃吧!");
}
6. Copy 类型
可以 copy 的类型(赋值后原变量仍可用):
- 所有整数类型(如
u32) - 布尔类型
bool - 所有浮点数类型(如
f64) - 字符类型
char - 元组,当且仅当其包含的类型也都是 Copy 的时候
- 不可变引用
&T
7. 变量与常量
// 变量默认不可变, 使用lei关键字
let mut s = 6; // 添加 mut 使变量可变
// 常量, 使用const关键字, 不可以使用mut, 因为常量不可变
const MAX_POINT: u32 = 100;
// 引用
&符号表示引用:允许你引用某些值而不取得所有权
&mut:可变借用
// 静态变量
static HELLO_WORLD: &str = "Hello world!";
// 可变静态变量需要 unsafe
static mut COUNTER: u32 = 0;
fn add(inc: u32) {
// 凡是访问或修改可变的静态变量要放在unsafe块中
unsafe {
COUNTER += inc;
}
}
所有权系统
8. 内存管理
- 栈内存 stack
- 堆内存 heap
所有权规则
- 每个值都有一个变量,这个变量为该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域时,该值将被删除
借用规则
- 可变引用同时只能存在一个
- 可变引用与不可变引用不能同时存在
// 错误示例:同一时间无法对 `s` 进行两次可变借用
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // Error
// 错误示例:无法借用可变 `s` 因为它已经被借用了不可变
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
// 综上,同一时刻,你只能拥有要么一个可变引用,要么任意多个不可变引用
9. 切片
// 切片就是对 String 类型中某一部分的引用
let s = String::from("hello world");
// [开始索引, 结束索引) - 左闭右开
let hello = &s[0..5];
let world = &s[6..11];
// 省略前后,等于完整切片
let whole = &s[..];
// 字符串切片的类型标识是 &str
let s: &str = "Hello, world!";
字符串拼接
// 方法1: push_str
let mut s = String::from("foo");
s.push_str("bar"); // "foobar"
// 方法2: push(单个字符)
s.push('l');
// 方法3: 加号+ ,加号类似于fn add(self, s: $Str) -> String{}
let s1 = String::from("hello");
let s2 = String::from("world");
let s3 = s1 + &s2; // 注意:s1 的所有权被转移
// 方法4: format!(推荐),format!不会取得所有权
let s = format!("{}{}", s2, s3);
复合类型
10. 元组 Tuple
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 解构
let (x, y, z) = tup;
// 访问元素
let first = tup.0;
11. 结构体 struct
struct User {
active: bool,
username: String,
email: String,
}
// 创建实例
// 如果结构体实例是可变的,那么实例中所有字段都是可变的
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
};
// 元组结构体
struct Point(i32, i32, i32);
let origin = Point(0, 0, 0);
12. 枚举 enum
// 定义扑克牌花色枚举
enum PokerSuit {
Clubs,
Spades,
Diamonds,
Hearts,
}
let heart = PokerSuit::Hearts;
// Option 枚举用于处理空值
// 解释就是:一个变量要么有值:Some(T), 要么为空:None
enum Option<T> {
Some(T),
None,
}
// 为枚举定义方法, 也是使用impl关键字
enum Message {
Write(String),
Move(i32, i32),
}
impl Message {
fn call(&self) {}
}
13. 数组
固定长度的数组叫Array, 动态数组叫Vector
// 固定长度数组
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 初始化重复值
// [类型; 长度] 或者 [初始值; 长度]
let a = [3; 5]; // 3位初始值;长度为5, 所以就等同于[3, 3, 3, 3, 3]
// 数组切片
let slice: &[i32] = &a[1..3];
控制流
14. 条件与循环
// if 表达式
if condition {
// code
} else {
// code
}
// for 循环
for item in collection {
// 转移所有权
// 等价使用方式 for item in IntoIterator::into_iter(collection)
}
for item in &collection {
// 不可变借用
// 等价使用方式for item in collection.iter()
}
for item in &mut collection {
// 可变借用
// 等价使用方式 for item in collection.iter_mut()
}
// Range 循环
for number in (1..4).rev() {
// (1..4)是标准库的Range写法, 即从1到4,但是不包含结束
// rev方法是用于反转Range的
println!("{}", number); // 3 2 1
}
// loop 循环
// 例1
loop {
if n > 5 {
break
}
println!("{}", n);
}
// 例2
// loop 是一个表达式,因此可以返回一个值
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; //break 可以单独使用,也可以带一个返回值,有些类似 return
}
};
模式匹配
15. match 表达式
match target {
模式1 => 表达式1,
模式2 | 模式3 => {
语句1;
语句2;
表达式2
},
模式4..=模式5 => 表达式3, // 匹配范围
_ => 表达式4 // 通配符
}
if let 简写
记住:当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match。
// 单个匹配
if let Some(x) = v {
println!("three");
}
// 带 else 的写法
let Some(x) = six else {
println!("No result");
return;
};
忽略值
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
},
}
16. @绑定
// @符号用于创建一个变量,用于在模式匹配成功后,将值绑定这个变量
fn test_id(id: i32) {
match id {
x @ 1..=5 => println!("{}", x), // 绑定匹配值到 x
_ => println!("other id")
}
}
面向对象
17. 方法 Method
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 方法 - 第一个参数是 &self
fn area(&self) -> u32 {
self.width * self.height
}
// 关联函数 -> 在impl块里定义不把self作为第一个参数的函数, 不是方法, 用:::调用,例如String::from()
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size
}
}
}
let rect1 = Rectangle { width: 30, height: 50 };
let area = rect1.area();
// 调用关联函数, 用::调用
let s = Rectangle::square(20);
泛型与特征
18. 泛型
// 泛型结构体
struct Point<T, U> {
x: T,
y: U,
}
let p = Point { x: 1, y: 1.1 };
19. 特征 Trait
// 定义特征, 类似于接口
pub trait Summary {
fn summarize(&self) -> String;
}
// 实现特征
pub struct Weibo {
pub username: String,
pub content: String
}
impl Summary for Weibo {
// 在Weibo的实例上使用.summarize方法
fn summarize(&self) -> String {
format!("{}发表了微博{}", self.username, self.content)
}
}
特征约束
// 特征约束
// 形如 T: Summary 被称为特征约束,约束了泛型为具体类型。
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
同名方法处理
trait Pilot {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("Pilot Speaking")
}
}
impl Human {
fn fly(&self) {
println!("Human Speaking")
}
}
let person = Human;
person.fly(); // "Human Speaking", 这里没有指定trait, 默认调用自身
Pilot::fly(&person); // "Pilot Speaking"
Supertrait
即一个trait要继承其他trait的功能
use std::fmt;
// 实现Outline, 也必须实现Display这个特征
// 也可以说Outline依赖Display
trait Outline: fmt::Display {
fn outline_print(&self) {
let output = self.to_string(); // 因为to_string()这个方法,所以必须实现Display这个特征
let len = output.len();
println!("{}", "*".repeat(len + 2));
}
}
struct Point {}
// 实现Outline
impl Outline for Point {}
// 这里还必须实现Display, 不然会报错
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// ...
}
}
20. 特征对象
特征对象可以通过 & 引用或者 Box<T> 智能指针的方式来创建。
// 在 Rust 中,有两个self,一个指代当前的实例对象,一个指代特征或者方法类型的别名:
trait Draw {
fn draw(&self) -> Self;
}
集合类型
21. 动态数组 Vector
// 创建 Vector
let v: Vec<i32> = Vec::new();
let mut v = Vec::new();
v.push(1);
let v = vec![1, 2, 3]; // 使用宏创建
let v = vec![0; 3]; // 默认值 0,长度 3
let v_from = Vec::from([0, 0, 0]);
访问元素
// 方式1: 下标(可能 panic)
let third: &i32 = &v[2];
// 方式2: get 方法(安全)
// 使用get,它返回了 Option<&T>,因此还需要额外的 match 来匹配解构出具体的值
match v.get(2) {
Some(third) => println!("第三个元素是 {third}"),
None => println!("去你的第三个元素,根本没有!"),
}
存储枚举类型
let row = vec![
enumName::Int(2),
enumName::Text(String::from("blue")),
];
遍历 Vector
let v = vec!['a', 'b', 'c'];
// v.iter().enumerate()返回元祖形式
for (index, value) in v.iter().enumerate() {
println!("{}, {}", index, value);
}
22. HashMap KV存储
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// 获取值
let team_name = String::from("Blue");
let score: Option<&i32> = scores.get(&team_name);
// 如上
// 1 .get 方法返回一个 Option<&V> 类型的值,其中 V 是集合中值的类型。若找到了对应的键,就返回 Some(&value);若没找到,就返回 None。
// 2. get 方法的 key 参数必须是一个引用
// 安全获取值
// 若找到对应分数,就将其赋值给变量 score;若未找到,score 则会被赋值为 0
// copied 方法的作用是把 Option<&T> 转换为 Option<T>,也就是把引用类型转换为值类型。若 Option 是 Some(&value),就返回 Some(value);若 Option 是 None,则返回 None。
// 若 Option 是 Some(value),unwrap_or 方法会返回 value;若 Option 是 None,则返回传入的默认值,这里的默认值是 0
let score: i32 = scores.get(&team_name)
.copied()
.unwrap_or(0);
遍历 HashMap
for (k, v) in &scores {
println!("{}:{}", k, v);
}
entry 方法
// 检查 key 对应的 value 是否存在,不存在则插入
map.entry(key).or_insert(default_value);
生命周期
23. 生命周期注解
// 生命周期参数需要先声明 <'a>
// 这里x、y 和返回值至少活得和 'a 一样久(因为返回值要么是 x,要么是 y)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
生命周期规则
- 每个引用类型的参数都有自己的生命周期
- 如果只有1个输入生命周期参数,该生命周期被赋予所有输出生命周期参数
- 如果有多个输入生命周期参数,其中一个是
&self或&mut self,则self的生命周期被赋予所有输出生命周期参数
静态生命周期
拥有该生命周期的引用可以和整个程序活得一样久。
let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
错误处理
24. panic! 不可恢复错误
panic!("crash and burn");
25. Result 可恢复错误
// Result 枚举定义
enum Result<T, E> {
Ok(T),
Err(E),
}
文件读取示例
以下函数从文件中读取用户名,然后将结果进行返回:
use std::fs::File;
use std::io::{self, Read};
// 完整写法
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
// 使用 ? 简写
// ? 就是一个宏,它的作用跟上面的 match 几乎一模一样
// ? 只能用于返回Result的函数, 比如下面函数最后返回了Ok()
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
// 最简写法
fn read_username_from_file() -> Result<String, io::Error> {
std::fs::read_to_string("hello.txt")
}
模块系统
26. 包和模块
基本概念
- Package: 软件包
- Crate: 单元包
- Module: 模块
Crate 类型
- Binary: 二进制可执行文件
- Library: 库文件
Package 结构
使用 cargo new package_name 创建一个 package:
- 包含一个
Cargo.toml,描述如何构建 crates - 至少包含一个 crate(library 或 binary)
- 最多只能有一个 library crate(
src/lib.rs) - 可以有多个 binary crate(
src/bin/目录下)
模块定义
// 使用 mod 关键字创建模块
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 绝对路径
// 绝对路径引用时,从包根开始,路径名以包名或者 crate 作为开头, 可以直接以 crate 开头,然后逐层引用,每一层之间使用 :: 分隔:
use crate::front_of_house::hosting;
// 相对路径
// 相对路径,从当前模块开始,以 self,super 或当前模块的标识符作为开头, super即上级目录
// super 代表的是父模块为开始的引用方式,例如: super::serve_order();
// self 其实就是引用自身模块中的项
use self::front_of_house::hosting;
use super::some_module; // 上级模块
结构体和枚举的可见性
pub关键字控制模块和项的可见性- 结构体设置为
pub时,字段依然是私有的 - 枚举设置为
pub时,所有字段对外可见
27. use 引入模块
引入函数的时候, 一般引用的是函数的父级模块, 这是惯用做法,目的是区分这是模块引入的,还是本地定义的 但是对于struct、enum, 一般指定到本身
基本用法
use std::collections::HashMap;
use std::fmt::Result;
use std::io::Result as IoResult; // 别名
简化引入
// 分组引入
use std::collections::{HashMap, BTreeMap, HashSet};
use std::{cmp::Ordering, io};
// self 引入
use std::io::{self, Write};
// 通配符引入(谨慎使用)
use std::collections::*;
高级特性
28. 格式化输出
基本输出宏
print!: 输出到标准输出,不带换行符println!: 输出到标准输出,带换行符format!: 输出到 String
占位符区别
{}: 适用于实现了std::fmt::Display的类型{:?}: 适用于实现了std::fmt::Debug的类型(调试场景)
高级格式化
// 位置参数
println!("{1}{0}", 1, 2); // "21"
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // => Alice, this is Bob. Bob, this is Alice
// 具名参数
println!("{argument}", argument = "test");
println!("{name} {}", 1, name = 2); // => "2 1"
// 精度控制
let v = 3.1415926;
println!("{:.2}", v); // 保留小数点后两位 => 3.14
println!("{:+.2}", v); // 带符号保留小数点后两位 => +3.14
println!("{:.0}", v); // 不带小数 => 3
println!("{:.1$}", v, 4); // 通过参数来设定精度 => 3.1416,相当于{:.4}
29. 闭包
基本语法
// 完整形式
|param1, param2| {
语句1;
语句2;
返回表达式
}
// 简写形式
|param| 返回表达式 // 如果只有一个返回表达式
捕获方式
闭包捕获变量有三种途径:
| 特征 | 描述 | 所有权 |
|---|---|---|
FnOnce | 转移所有权 | 所有闭包自动实现 |
FnMut | 可变借用 | 没有移出所有权的闭包 |
Fn | 不可变借用 | 不需要改变捕获变量的闭包 |
使用建议
- 先使用
Fn特征 - 编译器会提示正确的特征选择
30. 迭代器 Iterator
基本用法
let arr = [1, 3];
let mut arr_iter = arr.into_iter();
assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);
迭代器适配器
// 消耗型适配器:collect
let v1: Vec<_> = arr.iter().map(|x| x + 1).collect();
所有权关系
| 使用方法 | 等价方式 | 所有权 |
|---|---|---|
for item in collection | IntoIterator::into_iter(collection) | 转移所有权 |
for item in &collection | collection.iter() | 不可变借用 |
for item in &mut collection | collection.iter_mut() | 可变借用 |
31. 类型转换
let a = 3.1 as i8; // 浮点转整数
let b = 100_i8 as i32; // 整数扩展
let c = 'a' as u8; // 字符转整数
32. 线程
使用 thread::spawn 可以创建线程
创建线程
use std::thread;
use std::time::Duration;
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
重要概念
thread::spawn返回JoinHandle,JoinHandle持有值的所有权JoinHandle::join()会阻塞当前线程直到子线程结束- 使用
move关键字将变量所有权转移给新线程 - 主线程结束时,所有子线程都会被终止
- 使用
thread::spawn来创建线程,创建出的多个线程之间并不存在执行顺序关系,因此代码逻辑千万不要依赖于线程间的执行顺序。 - 实现
Send trait的类型可在线程间转移所有权
所有权转移示例
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
33. 消息传递Channel
使用 mpsc::channel 函数来创建Channel,返回一个元祖: (发送端,接收端)
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
// 注意:val的所有权已经被转移
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
34. Mutex互斥锁
重要原则:
- 在使用数据之前,必须尝试获取锁(lock)
- 使用完mutex所保护的数据,必须对数据进行解锁,以便其他线程可以获取锁
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
重要对比
- 我们使用
RefCell<T>来改变Rc<T>里面的内容 - 我们使用
Mutex<T>来改变Arc<T>里面的内容 - ⚠️
Mutex<T>有死锁风险
35. 错误处理进阶
or() 和 and() 操作
-
or():表达式按照顺序求值,若任何一个表达式的结果是
Some或Ok,则该值会立刻返回Some1 or Some2 = Some1Err or Ok = Ok
-
and():若两个表达式的结果都是
Some或Ok,则第二个表达式中的值被返回。若任何一个的结果是None或Err,则立刻返回。Some1 and Some2 = Some2Err and Ok = ErrErr1 and Err2 = Err1
let s1 = Some("hello");
let s2 = Some("world");
let n: Option<&str> = None;
assert_eq!(s1.or(s2), s1); // Some("hello")
assert_eq!(s1.and(s2), s2); // Some("world")
assert_eq!(n.or(s2), s2); // Some("world")
36. Macro 宏
宏类型区别
- derive宏:只能用于struct和enum
- 属性宏:可以用于任意条目,比如函数
常用宏示例
// derive宏
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
// 属性宏
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
// 声明宏
macro_rules! vec_of_strings {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}
let v = vec_of_strings!["hello", "world"];
37. async/await 异步编程
use futures::executor::block_on;
async fn hello_world() {
hello_cat().await;
println!("hello, world!");
}
async fn hello_cat() {
println!("hello, kitty!");
}
fn main() {
let future = hello_world();
block_on(future);
}
备注:需要添加
futures = "0.3"到 Cargo.toml
异步运行时对比
| 运行时 | 特点 |
|---|---|
| tokio | 功能最全面,生态最成熟 |
| async-std | 标准库的异步版本 |
| smol | 轻量级,模块化设计 |
38. test
测试类型
- 单元测试:需要在文件头部添加
#[cfg(test)] - 集成测试:不需要,但需要单独目录,目录名为
tests
测试函数标注
测试函数需要使用test属性(attribute)进行标注:
- 在函数上加
#[test],可把函数变成测试函数 - Attribute就是一段Rust代码元数据
运行测试
cargo test # 运行所有测试
cargo test fn_name # 指定测试单个测试函数
cargo test filter_name # 模糊匹配名称开头的多个函数
断言宏
assert!(bool); // 接受一个返回bool的参数,为true通过,为false则panic
assert_eq!(target, compare); // 判断两者相等
assert_ne!(target, compare); // 判断两者不等
// 自定义错误信息
assert!(is_able(), "这里可能为false");
忽略测试
#[test]
#[ignore]
fn expensive_test() {
// 需要较长时间运行的测试
}
39. 工作空间workspace
在外层 Cargo.toml 中申明 workspace:
[workspace]
members = [
"lib-one",
"lib-two"
]
工作空间命令
cargo test -p lib-one # 单独运行测试某个包
cargo build # 构建工作空间下所有包
cargo test # 运行工作空间下所有包的测试
目录结构示例
my-workspace/
├── Cargo.toml # workspace根配置
├── lib-one/
│ ├── Cargo.toml
│ └── src/lib.rs
├── lib-two/
│ ├── Cargo.toml
│ └── src/lib.rs
└── tests/ # 集成测试
└── integration_test.rs
40. 解引用*
基本用法
let x = 5;
let y = &x;
assert_eq!(5, *y); // *是解引用符号,用于取得引用指向的值
备注:不加*,会报错:整数与整数引用不可比较
Box解引用
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
自定义解引用
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
let x = 5;
let y = MyBox(x);
assert_eq!(5, *y); // 通过Deref trait实现
41. unsafe
使用 unsafe 关键字,切换到 unsafe rust,开启一个 unsafe 块,存放 unsafe 代码
unsafe 超能力
- 解引用原始指针
- 调用 unsafe 函数或方法
- 访问或修改可变的静态变量
- 实现 unsafe trait
原始指针
let mut num = 5;
let r1 = &num as *const i32; // 不可变原始指针
let r2 = &mut num as *mut i32; // 可变原始指针
unsafe fn dangerous() {}
// unsafe块
unsafe {
println!("r1: {}", *r1);
println!("r2: {}", *r2);
dangerous(); // unsafe方法需要在块里被调用
}
实际用例
// 与C语言交互
extern "C" {
fn abs(input: i32) -> i32;
}
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
42. 类型别名 type
可减少代码字符重复
type Result<T> = Result<T, std::io::Error>;
fn write(buf: &[u8]) -> Result<usize> {
// 这里返回直接用 Result<T>, 而不需要写完整了
Ok(buf.len())
}
// 更复杂的别名
type Thunk = Box<dyn Fn() + Send + 'static>;
type IoResult<T> = Result<T, std::io::Error>;
标准库中的类型别名
// std::io 中定义
type Result<T> = Result<T, Error>;
type IoResult<T> = Result<T, Error>;
// std::collections 中定义
type HashMap<K, V> = std::collections::HashMap<K, V>;
持续修改和补充