学习rust_day17

Unsafe Rust

Unsafe Rust:隐藏在安全背后的强大能力

  • Rust默认保证内存安全
  • Unsafe Rust不强制这些保证
  • 用于更底层的系统编程或绕过编译器限制

Unsafe Rust的五个超能力

  • 解引用裸/原始指针
  • 调用不安全函数或方法
  • 访问或修改可变静态变量
  • 实现不安全的trait
  • 访问union的字段

Unsafe≠不安全

  • borrow checker仍然工作
  • 只是某些检查不再进行
  • 代码是否安全由程序员来保证
  • 建议将unsafe块限制得尽可能小

解引用原始指针

  • Rust的普通引用总是有效,这是由编译器保证的

  • 但在Unsafe Rust中,出现了两种新类型的指针,称为原始指针

    (raw pointers ):

    • *const T: 不可变原始指针
    • *mut T: 可变原始指针
    • “不可变”是指:该指针在被解引用之后,不能直接被赋值。
  • 注意:星号*是类型的一部分,不是解引用操作符!

原始指针Vs普通用&智能指针
原始指针的特点:

  • 可以忽略借用规则(同时存在多个可变或不可变指针)

  • 不保证指向有效内存

  • 可以为null

  • 不会自动清理(没有Drop)

  • 使用原始指针是放弃安全性以换取性能,或是为了与像C语言或底层硬件交互。

例:

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut num = 5;

//可以在safe的代码中创建,但是不能在unsafe代码块其他地方解引用
let r1 = &raw const num;
let r2 = &raw mut num;

unsafe {
println!("r1 is {}", *r1);
println!("r2 is {}", *r2);
}
}

调用不安全的函数/方法

  • Unsafe函数看起来与普通函数完全相同
  • 区别在于定义前添加了unsafe关键字
  • 这表明函数有特定要求,Rust无法自动保证这些要求被满足
  • 调用unsafe函数时,我们表示已阅读其文档并承担责任

例:

1
2
3
4
5
6
7
fn main() {
unsafe {
dangerous();
}
}

unsafe fn dangerous() {}

创建unsafe代码的安全抽象

  • 包含unsafe代码的函数不一定要标记为unsafe
  • 用安全函数封装unsafe代码是常见的抽象模式

使用extern调用外部代码

  • 外部函数接口(FFI)Foreign Function Interface
  • Rust代码有时需要与其他语言编写的代码交互
  • extern关键字用于创建和使用外部函数接口(FFI)
  • FFI使一种编程语言能够定义函数并允许另一种(外部)编程语言调用这些函数

例:

1
2
3
4
5
6
7
8
9
10
unsafe extern "C" {
fn abs(input: i32) -> i32;
// safe fn abs(input: i32) -> i32; //如果这样写就不用在unsafe块中调用
}

fn main() {
unsafe {
println!("{}", abs(-3));
}
}

访问或修改可变静态变量

  • Rust支持全局变量,但可能与Rust的所有权规则产生问题
  • 如果两个线程访问同一个可变全局变量,可能导致数据竞争
  • Rust中的全局变量被称为*静态(static)*变量

静态变量的特性

  • 静态变量只能存储具有’static生命周期的引用
  • Rust编译器可以推断生命周期,不需要显式注释
  • 静态变量在内存中有固定地址
    • 使用该值将始终访问相同的数据
  • 常量可能在使用时复制其数据
  • 静态变量可以是可变的(与常量不同)

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static mut COUNTER: u32 = 0;

/// SAFETY: 写上安全注释段

unsafe fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}

fn main() {
unsafe {
/// SAFETY: 写上安全注释段
add_to_count(3);
println!("{}", *(&raw const COUNTER)); // 一般你不能创建一个指向可变静态变量的引用
}
}

实现unsafe trait

  • 可以使用unsafe来实现不安全trait
  • 当trait中至少有一个方法具有编译器无法验证的不变量时,该trait就是不安全的
  • 使用unsafe关键字声明trait为不安全的

例:

1
2
3
4
5
6
7
8
9
10
11
unsafe trait Foo {
// ...
}

unsafe impl Foo for i32 {
// ...
}

fn main() {

}

访问Union的字段.

  • Union类似于struct
    • 在特定实例中,同一时间只能使用一个声明的字段
  • 主要用途:与c代码中的Union交互
  • 访问Union的字段是unsafe的
    • 因为Rust无法保证当前存储在Union实例中的数据类型
  • 有关Union的更多信息,可以参考Rust参考手册

使用Miri检查不安全代码

  • Mii是一个官方的Rust工具,用于检测未定义行为
  • 作为一个动态工具,Mii在程序运行时工作
  • 它通过运行程序或测试套件来检查代码,检测是否违反了Rust的规则。

安装:

1
rustup +nightly commponent add miri

运行:

1
cargo +nightly miri run

高级 Trait

在trait定义中使用关联类型指定占位符类型

关联类型

  • 关联类型将一个“类型占位符”绑定到trait中
  • 允许trait的方法使用这些占位类型
  • 实现该trait时,具体实现者提供实际的类型

关联类型Vs泛型参数

  • 如果用泛型:
    • 可以对同一个类型多次实现Iterator(T不同)
    • 每次调用next都需要明确类型
  • 如果是关联类型
    • 限定trait只能对一个类型实现一次
    • 无需在使用中注明类型
    • trait定义更清晰、约定更严格
    • 成为trait接口的一部分

默认泛型类型参数与运算符重载

默认泛型类型参数

  • 当我们使用泛型类型参数时,可以为其指定一个默认的具体类型
  • 当默认类型适用时,实现该trait的人就无需额外指定类型
  • 语法:<占位类型=默认类型>。

运算符重载概览

  • Rust不允许创建自定义运算符,也不能重载任意运算符
  • 但可以通过实现std:ops中定义的trait来重载已有运算符
1
2
3
4
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32
}

impl Add for Point {
type Output = Point;

fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y
}
}
}

fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3},
Point { x: 3, y: 3 }
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::ops::Add;

struct Millimeters(u32);

struct Meters(u32);

impl Add<Meters> for Millimeters {
type Output = Millimeters;

fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}

fn main() {

}

完全限定语法(Fully Qualified Syntax)

  • 用于消除歧义
  • Rust允许多个Trait拥有相同的方法名
  • 也可以在同一类型上实现多个带有相同方法名的Trait
  • 类型本身也可以定义相同名称的方法

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
trait Pilot {
fn fly(&self);
}

trait Wizard {
fn fly(&self);
}

struct Human;

impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking");
}
}

impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}

impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}

fn main() {
let person = Human;
person.fly(); //输出*waving arms furiously*
Pilot::fly(&person); //输出This is your captain speaking
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
trait Animal {
fn baby_name() -> String;
}

struct Dog;

impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}

impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}

fn main() {
println!("A baby dog is called a {}", Dog::baby_name()); //输出A baby dog is called a Spot
println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); //输出A baby dog is called a //puppy
//这就是完全限定语法
}

Supertrait

  • 在Rust中,如果一个Trait依赖于另一个Trait的功能,我们可以将这个被依赖的Trait作为Supertrait引入
    • 这让我们可以在Trait内部直接使用另一个Trait提供的功能

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::fmt;

trait OutlinePrint: fmt::Display {
fn Outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len +2));
println!("* {output} *");
println!("*{}*", " ".repeat(len +2));
println!("{}", "*".repeat(len + 4));
}
}

struct Point {
x: i32,
y: i32
}

impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

impl OutlinePrint for Point {}

fn main() {
let p = Point { x: 1, y: 2};
p.Outline_print();
}

使用Newtype模式在外部类型上实现外部trait

  • 孤儿规则:只有当trait或类型是我们的crate时(本地),才允许我们在类型上实现trait
  • 可以使用newtype模式绕过这个限制
  • 该模式涉及在tuple struct中创建一个新类型
    • 该tuple struct将有一个字段,是我们想要实现trait的类型的轻量级包装器
    • 包装器类型就是属于我们crate的(本地的),可以在包装器上实现trait
  • 使用这种模式没有运行时性能损失,包装器类型在编译时被忽略。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}

fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {w}");
}

高级类型、函数和闭包

高级类型

使用Newtype模式实现类型安全和抽象

  • 增强类型安全
  • 提供抽象
  • 避免不同单位的数值混淆
1
2
struct Millimeters(u32);
struct Meters(u32);
  • 使用Millimeters 类型作为函数参数时,不能误传Meters 类型或u32 类型

Newtype模式隐藏实现细节

  • Newtype模式隐藏实现细节
  • 提供仅包含公共方法的API
  • 隐藏底层结构(如HashMap)
1
struct People(HashMap<i32, String>);
  • 外部代码只能通过我们提供的方法与People交互,无法直接访问HashMap

使用类型别名创建类型同义词
Creating Type Synonyms with Type Aliases

  • 使用type关键字定义类型别名:
    • type Kilometers = i32;
  • Kilometers与i32实际是相同类型
  • 无法获得Newtype模式提供的类型安全性
  • 类型同义词的主要用途是减少重复

永不返回的类型
The Never Type that Never Returns

  • 类型理论中称为空类型(Empty Type)
  • 表示“永不返回”的值
1
2
3
fn bar() -> ! {
// ...
}

动态大小类型和Sized trait
Dynamically Sized Types and the Sized Trait

  • 大小在编译时未知
  • 例如:str类型(不是&str)
1
let s1: str = "Hello there!" //无法通过编译,因为动态大小的类型必须放在某种指针后才能使用

高级函数和闭包

Function Pointer

  • 类型:fn(小写f),区别于闭包trait Fn
  • 语法:fn(参数类型)->返回类型
  • fn是类型
  • 函数指针实现了所有三种闭包特性(Fn,FnMut,FnOnce)
  • 可以在需要闭包的地方使用函数指针
1
2
3
4
5
6
7
8
9
10
11
12
13
fn add_one(x: i32) -> i32 {
x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}

fn main() {
let answer = do_twice(add_one, 5);

println!("The answer is: {answer}");
}

返回闭包

  • 闭包是由特性表示的,不能直接返回
  • 每个闭包都有自己独特的类型
  • 解决方案1:使用impl Trait
1
2
3
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
  • 解决方案2:使用Trait Object

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let handlers = vec![returns_closure(), returns_initialized_closure(123)];
for handler in handlers {
let output = handler(5);
println!("{output}");
}
}

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}

fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x + init)
}

学习rust_day17
https://zlsf-zl.github.io/2026/03/27/学习rust-day17/
作者
ZLSF
发布于
2026年3月27日
许可协议