1 不安全的Rust
不安全的Rust的超级力量:
- 解引用裸指针
- 调用不安全的函数/方法
- 访问/修改可变静态变量
- 实现不安全特性(trait)
unsafe不会关闭借用检查器或者禁用其他安全检查
1.1 解引用裸指针
- 裸指针:
*const T与*mut T - 创建裸指针是安全的;解引用裸指针是不安全的
- 裸指针与智能指针的差别
- 可忽略借用规则:可同时拥有同一个资源的不可变的和可变的裸指针;或者同时有多个指向相同位置的可变裸指针
- 可以指向无效内存;可以为空
- 不能实现任何自动清理功能
let mut num = 5;// 可同时有同一个资源的可变和不可变的裸指针let r1 = &num as *const i32;let r2 = &mut num as *mut i32;// 解引用裸指针的代码是不安全的,必须用unsafe包围unsafe {println!("r1 is: {}", *r1);println!("r2 is: {}", *r2);}let address = 0x012345usize;// 裸指针可指向无效位置let r = address as *const i32;
1.2 调用不安全的函数/方法
- 调用不安全函数/方法的代码是不安全的,必须放在
unsafe块中
unsafe fn dangerous() {}unsafe {dangerous();}
1.2.1 包含不安全代码的安全函数/方法
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {let len = slice.len();assert!(mid <= len);// 编译错误:不能同时拥有两个可变引用(&mut slice[..mid],&mut slice[mid..])}
- 可以使用不安全代码来实现上述函数
use std::slice;fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {let len = slice.len();let ptr = slice.as_mut_ptr();assert!(mid <= len);// 这是包含在安全函数中的不安全代码unsafe {(slice::from_raw_parts_mut(ptr, mid),slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))}}
1.2.2 外部函数接口(Foreign Function Interface,FFI)
- 外部函数接口:让Rust可以与其他语言交互
extern块中声明的函数总是不安全的extern "C"中的"C"是一种应用程序二进制接口(Application Binary Interface,ABI),"C"是最常见的ABI- 以下是Rust调用C的示例
extern "C" {fn abs(input: i32) -> i32;}fn main() {unsafe {println!("Absolute value of -3 according to C: {}", abs(-3));}}
- 以下是C调用Rust的示例。
#[no_mangle]表示不允许对函数名称进行修饰
#[no_mangle]pub extern "C" fn call_from_c() {println!("Just called a Rust function from C!");}
1.3 访问/修改可变静态变量
- 全局变量又称作“静态变量”,其生命周期是
'static,书写时通常省略生命周期 - 访问不可变的静态变量是安全的
- 访问和修改可变的静态变量是不安全的,相关代码必须放在
unsafe块中
static mut COUNTER: u32 = 0;fn add_to_count(inc: u32) {unsafe {COUNTER += inc;}}fn main() {add_to_count(3);unsafe {println!("COUNTER: {}", COUNTER);}}
1.4 实现不安全的trait
- 至少有一个方法中包含编译器不能验证的不变量时
trait是不安全的,必须用unsafe修饰 - 实现不安全的
trait时必须用unsafe修饰
unsafe trait Foo {// methods go here}unsafe impl Foo for i32 {// method implementations go here}
2 高级生命周期
2.1 生命周期子类型
2.1.1 第一步
- 有如下代码
struct Context<'a>(&'a str);//涉及引用时,结构体字段必须指定生命周期注解struct Parser<'a> {//涉及引用时,结构体字段必须指定生命周期注解context: &'a Context<'a>,// 这里必须指定嵌套的生命周期注解}impl<'a> Parser<'a> {fn parse(&self) -> Result<(), &str> {Err(&self.context.0[1..])}}
- 然后有以下代码
fn parse_context(context: Context) -> Result<(), &str> {Parser { context: &context }.parse()}
- 上述代码不能编译
- 根据第10章3.4.1节描述的生命周期省略规则,
Parser::parse()方法省略了生命周期注解,返回值涉及的生命周期等于self的生命周期 parse_context函数结束时,self对应的临时对象的生命周期结束,所以Parser::parse()方法返回值的生命周期也结束,无法再作为parse_context函数的返回值
- 根据第10章3.4.1节描述的生命周期省略规则,
2.1.2 第二步:使用两个生命周期注解
struct Context<'a>(&'a str);struct Parser<'a,'s>{context: &'a Context<'s>}impl<'a,'s> Parser<'a,'s>{fn parse(&self) -> Result<(),&str>{Err(&self.context.0[1..])}}
- 上述代码不能通过编译
- 结构体
Parser使用了两个生命周期注解,但没有指定它们的关系 - 结构体实例有效期间,其字段必须有效,所以,生命周期
's必须不短于'a - 然而,代码没有指示
's和'a的关系
- 结构体
2.1.3 第三步:指示两个生命周期注解的关系
struct Context<'a>(&'a str);struct Parser<'a,'s:'a>{//使用两个生命周期注解,并且定义s不必a短context: &'a Context<'s>}impl<'a,'s> Parser<'a,'s>{fn parse(&self) -> Result<(),&'s str>{//必须指定返回值使用哪个生命周期Err(&self.context.0[1..])}}
's:'a表示生命周期's不比'a短,将冒号:理解成>=即可
2.2 对泛型类型的生命周期限定
digraph{node [margin=0 fontcolor=blue fontsize=16 width=10 shape=box]a [label="struct Ref<T>(T);"]b [label="//错误:对于引用类型的结构体字段,必须指定生命周期\nstruct Ref<T>(&T);" fontcolor=red]c [label="//错误:T可能是引用类型,或者含有引用类型,必须为类型指定生命周期限定\nstruct Ref<'a,T>(&'a T);" fontcolor=red]d [label="struct Ref<'a,T:'a>(&'a T);"]a -> b[label="使用引用类型" fontsize="12"]b -> c[label="为引用类型指定生命周期" fontsize="12"]c -> d[label="为引用类型的实例&T指定生命周期;同时为类型T指定生命周期限定" fontsize="12"]}
2.3 特性对象的生命周期推导
- 特性对象的生命周期默认是
'static - 为特性指定生命周期的写法:
Box<Foo + 'a>,其中Foo是特性类型
3 高级特性
3.1 关联类型
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;}impl Iterator for Counter {type Item = u32;fn next(&mut self) -> Option<Self::Item> {}}pub trait Iterator<T> {fn next(&mut self) -> Option<T>;}
- 关联类型与泛型的差别:
- 使用关联类型时,类型只能实现特性一次,关联到某种特定的类型
- 使用泛型时,类型可以多次实现特性,每次指定不同的泛型类型参数
3.2 默认泛型类型参数
- 可通过实现标准库
std::ops中的相关特性来实现运算符重载 - 标准库
std::ops中的Add特性定义如下
trait Add<RHS=Self> {type Output;fn add(self, rhs: RHS) -> Self::Output;}
- 其中的
RHS=Self指示了默认的加数类型等于自身,这就是默认泛型类型参数 - 实现特性的时候,如果不给出泛型类型参数,则使用默认值,例如:
use std::ops::Add;#[derive(Debug,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,}}}
- 实现特性的时候,也可以给出泛型类型参数,以覆盖默认值,例如:
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))}}
- 默认参数类型主要用于如下两个方面
- 扩展类型而不破坏现有代码:通过覆盖泛型类型参数,可以扩展类型以处理新的参数类型,但不影响现有代码
- 在大部分用户都不需要的特定情况进行自定义:这是用途1的具体应用
3.3 用完全限定语法消除歧义
3.3.1 特性方法的完全限定语法
- 不能阻止两个特性含有相同签名的方法;不能阻止类型同时实现这两个特性
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*");}}
- 上述代码中,类型
Human实现了Pilot和Wizard特性,这两个特性都含有fly方法;Human本身也含有fly方法 - 通过
Human实例调用fly方法时,如何指示应该使用哪个fly方法?
fn main() {let person = Human;Pilot::fly(&person);//用特性名称限定应该调用哪个方法Wizard::fly(&person);//用特性名称限定应该调用哪个方法person.fly();// 也可以写成Human::fly(&person);但这样写复杂一些}
3.3.2 关联函数的完全限定语法
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());}
3.3.3 完全限定语法的一般形式
<Type as Trait>::function(receiver_if_method, next_arg, ...);
- 可省略可以从代码其他部分推导出来的部分,如
- 省略
Type as,变成Trait::function(receiver_if_method, next_arg, ...) - 省略
receiver_if_method变成<Type as Trait>::function(next_arg,...)
- 省略
3.4 父特性
use std::fmt;trait OutlinePrint: fmt::Display {fn outline_print(&self) {// 调用父特性fmt::Display的to_string()方法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));}}
- 上面的例子中,
fmt::Display是OutlinePrint的父特性,为类型实现一个特性时,必须同时实现其父特性
struct Point {x: i32,y: i32,}// 这是错误的:Point没有实现OutlinePrint的父特性,所以不能实现子特性impl OutlinePrint for Point {}
- 必须为
Point类型实现std::fmt::Display,然后才能实现OutlinePrint
use std::fmt;impl fmt::Display for Point {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "({}, {})", self.x, self.y)}}
3.5 使用newtype模式绕过孤儿规则
- 孤儿规则:特性和类型两者中至少有一个在本地时,才能为类型实现特性,不能为其他包中的类型实现其他包中的特性
newtype模式:用元组结构体封装其他包中的类型,然后对这个元组结构体实现其他包中的特性
use std::fmt;struct Wrapper(Vec<String>);// 对封装了其他包中类型的元组结构体实现其他包中的特性impl fmt::Display for Wrapper {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {// 这里用self.0引用被元组结构体封装的Vec<String>类型write!(f, "[{}]", self.0.join(", "))}}// 实现Deref特性,让类型变成智能指针impl std::ops::Deref for Wrapper{type Target = Vec<String>;fn deref(&self)->&Self::Target{&self.0}}fn main() {let w = Wrapper(vec![String::from("hello"), String::from("world")]);println!("w = {}", w);//这里就可以使用fmt::Display特性了let x = vec![String::from("a")];for y in x.iter(){println!("{}",y);}let w = Wrapper(x);// 必须实现Deref特性,才可以这样写// 解引用强制多态:// 1 类型Wrapper没有iter()方法// 2 对其调用Deref特性的deref方法,得到&Vec<String>// 3 &Vec<String>含有iter()方法,使用之for y in w.iter(){println!("{}",y);}}
4 高级类型
4.1 类型别名
type Kilometers = i32;let x: i32 = 5;let y: Kilometers = 5;println!("x + y = {}", x + y);
std::io::Result就是std::result::Result<T,std::io::Error>的别名
4.2 空类型
!表示空类型(empty type,或者never type)- 从不返回的函数称作发散函数(diverging functions)
- 发散函数的返回类型表示为
!
4.2.1 示例1:发散函数
fn bar() -> ! {// --snip--}
4.2.2 示例2:match中的空类型
fn main(){let value = None;let mut guess:u32;loop{guess = match value{Some(v) => v,None => break,};break;}println!("{}",guess);}
Ok分支的返回值类型是u32Err分支返回空类型,而空类型!可以强转为其他任何类型- 最终
match表达式的类型是u32 Err分支的break不返回值,guess没有被赋值,控制返回到外层的loop循环语句,循环被中断- 流程到达最后的
println时,guess可能还没有初始化,所以编译会出错:使用可能未初始化的guess - 在声明
guess的时候给出初始值就可以通过编译
4.2.3 示例3:用于panic!的空类型
impl<T> Option<T> {pub fn unwrap(self) -> T {match self {Some(val) => val,None => panic!("called `Option::unwrap()` on a `None` value"),}}}
4.2.4 示例4:loop中的空类型
let x = loop{break;};println!("{:?}",x);
loop表达式的类型是空类型,所以x也是空类型println!中必须使用{:?},不能使用{},因为空类型没有实现std::fmt::Display特性
4.3 动态大小类型与Sized特性
- 动态大小类型(dynamically sized types),缩写为
DST,也称作unsized types str就是动态大小类型,不能创建str类型的变量- 字面字符串的类型是
&str,是一种固定大小的引用类型 &T大小通常为usize大小,即一个指针大小&str要存储str的地址和大小,所以&str的大小是两个usize的大小,&str是一种肥指针(fat pointer)- 必须用某种指针类型(各种引用、原始指针、Box、Rc等)指向动态大小类型的值
- 特性是动态大小类型:实现特性的具体类型无法在编译时确定,所以特性值的大小无法在编译时确定
Sized特性表示在编译时知道大小的类型,编译器会自动为每个在编译时知道大小的类型实现Sized特性- 编译器会自动为每个泛型类型参数增加
Sized特性限定fn generic<T>(t: T) {}- 实际上被处理为
fn generic<T: Sized>(t: T) {}
?Sized表示也可用于大小不确定类型(动态大小类型):fn generic<T: ?Sized>(t: &T) {}
fn add_one(x: i32) -> i32 {x + 1}fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {f(arg) + f(arg)}fn twice_do<T>(f: T,arg: i32) -> i32 where T:Fn(i32) -> i32{f(arg) + f(arg)}fn demo1(){// 函数名作为函数指针使用let answer = do_twice(add_one, 5);println!("The answer is: {}", answer);// 函数名作为Fn特性使用let answer = twice_do(add_one, 5);println!("The answer is: {}", answer);// 闭包作为函数指针使用let answer = do_twice(|arg: i32|->i32 { arg + 1}, 5);let answer = do_twice(|x|x+1, 5);println!("The answer is: {}", answer);// 闭包作为Fn特性使用let answer = twice_do(|arg: i32|->i32 { arg + 1}, 5);let answer = twice_do(|x|x+1, 5);println!("The answer is: {}", answer);}pub fn main() {demo1();}
- 函数名称可以作为函数指针、
Fn特性使用 - 闭包可以作为函数指针、
Fn特性使用 - 在
Rust中,形式参数使用函数指针、Fn系列特性都可以;实际参数使用函数名称、闭包都可以 - 与外部代码(如C语言)交互时,必须使用函数指针
5.2 返回闭包
fn returns_closure() -> Fn(i32) -> i32 {|x|x+1 // 错误:Fn没有实现Sized特性约束}
- 闭包不能直接作为
Fn系列特性返回:特性是大小不确定类型,不能直接使用 - 可以用
Box封装闭包
fn returns_closure() -> Box<Fn(i32) -> i32> {Box::new(|x|x+1)}println!("The answer is: {}", returns_closure()(5));
