Unsafe Rust
Unsafe Rust:隐藏在安全背后的强大能力
Rust默认保证内存安全
Unsafe Rust不强制这些保证
用于更底层的系统编程或绕过编译器限制
Unsafe Rust的五个超能力
解引用裸/原始指针
调用不安全函数或方法
访问或修改可变静态变量
实现不安全的trait
访问union的字段
Unsafe≠不安全
borrow checker仍然工作
只是某些检查不再进行
代码是否安全由程序员来保证
建议将unsafe块限制得尽可能小
解引用原始指针
原始指针Vs普通用&智能指针 原始指针的特点:
例:
1 2 3 4 5 6 7 8 9 10 11 12 fn main () { let mut num = 5 ; 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 ; }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 ;unsafe fn add_to_count (inc: u32 ) { unsafe { COUNTER += inc; } }fn main () { unsafe { 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
运行:
高级 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 (); Pilot::fly (&person); }
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 ()); println! ("A baby dog is called a {}" , <Dog as Animal>::baby_name ()); }
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关键字定义类型别名:
Kilometers与i32实际是相同类型
无法获得Newtype模式提供的类型安全性
类型同义词的主要用途是减少重复
永不返回的类型 The Never Type that Never Returns
!
类型理论中称为空类型(Empty Type)
表示“永不返回”的值
动态大小类型和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 }
例:
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) }