1 不安全的Rust

不安全的Rust的超级力量:

  • 解引用裸指针
  • 调用不安全的函数/方法
  • 访问/修改可变静态变量
  • 实现不安全特性(trait)

unsafe不会关闭借用检查器或者禁用其他安全检查

1.1 解引用裸指针

  • 裸指针:*const T*mut T
  • 创建裸指针是安全的;解引用裸指针是不安全的
  • 裸指针与智能指针的差别
    • 可忽略借用规则:可同时拥有同一个资源的不可变的和可变的裸指针;或者同时有多个指向相同位置的可变裸指针
    • 可以指向无效内存;可以为空
    • 不能实现任何自动清理功能
  1. let mut num = 5;
  2. // 可同时有同一个资源的可变和不可变的裸指针
  3. let r1 = &num as *const i32;
  4. let r2 = &mut num as *mut i32;
  5. // 解引用裸指针的代码是不安全的,必须用unsafe包围
  6. unsafe {
  7. println!("r1 is: {}", *r1);
  8. println!("r2 is: {}", *r2);
  9. }
  10. let address = 0x012345usize;
  11. // 裸指针可指向无效位置
  12. let r = address as *const i32;

1.2 调用不安全的函数/方法

  • 调用不安全函数/方法的代码是不安全的,必须放在unsafe块中
  1. unsafe fn dangerous() {}
  2. unsafe {
  3. dangerous();
  4. }

1.2.1 包含不安全代码的安全函数/方法

  1. fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  2. let len = slice.len();
  3. assert!(mid <= len);
  4. // 编译错误:不能同时拥有两个可变引用
  5. (&mut slice[..mid],&mut slice[mid..])
  6. }
  • 可以使用不安全代码来实现上述函数
  1. use std::slice;
  2. fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  3. let len = slice.len();
  4. let ptr = slice.as_mut_ptr();
  5. assert!(mid <= len);
  6. // 这是包含在安全函数中的不安全代码
  7. unsafe {
  8. (slice::from_raw_parts_mut(ptr, mid),
  9. slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
  10. }
  11. }

1.2.2 外部函数接口(Foreign Function Interface,FFI)

  • 外部函数接口:让Rust可以与其他语言交互
  • extern块中声明的函数总是不安全的
  • extern "C"中的"C"是一种应用程序二进制接口(Application Binary Interface,ABI),"C"是最常见的ABI
  • 以下是Rust调用C的示例
  1. extern "C" {
  2. fn abs(input: i32) -> i32;
  3. }
  4. fn main() {
  5. unsafe {
  6. println!("Absolute value of -3 according to C: {}", abs(-3));
  7. }
  8. }
  • 以下是C调用Rust的示例。#[no_mangle]表示不允许对函数名称进行修饰
  1. #[no_mangle]
  2. pub extern "C" fn call_from_c() {
  3. println!("Just called a Rust function from C!");
  4. }

1.3 访问/修改可变静态变量

  • 全局变量又称作“静态变量”,其生命周期是'static,书写时通常省略生命周期
  • 访问不可变的静态变量是安全的
  • 访问和修改可变的静态变量是不安全的,相关代码必须放在unsafe块中
  1. static mut COUNTER: u32 = 0;
  2. fn add_to_count(inc: u32) {
  3. unsafe {
  4. COUNTER += inc;
  5. }
  6. }
  7. fn main() {
  8. add_to_count(3);
  9. unsafe {
  10. println!("COUNTER: {}", COUNTER);
  11. }
  12. }

1.4 实现不安全的trait

  • 至少有一个方法中包含编译器不能验证的不变量时trait 是不安全的,必须用unsafe修饰
  • 实现不安全的trait时必须用unsafe修饰
  1. unsafe trait Foo {
  2. // methods go here
  3. }
  4. unsafe impl Foo for i32 {
  5. // method implementations go here
  6. }

2 高级生命周期

2.1 生命周期子类型

2.1.1 第一步

  • 有如下代码
  1. struct Context<'a>(&'a str);//涉及引用时,结构体字段必须指定生命周期注解
  2. struct Parser<'a> {
  3. //涉及引用时,结构体字段必须指定生命周期注解
  4. context: &'a Context<'a>,// 这里必须指定嵌套的生命周期注解
  5. }
  6. impl<'a> Parser<'a> {
  7. fn parse(&self) -> Result<(), &str> {
  8. Err(&self.context.0[1..])
  9. }
  10. }
  • 然后有以下代码
  1. fn parse_context(context: Context) -> Result<(), &str> {
  2. Parser { context: &context }.parse()
  3. }
  • 上述代码不能编译
    • 根据第10章3.4.1节描述的生命周期省略规则,Parser::parse()方法省略了生命周期注解,返回值涉及的生命周期等于self的生命周期
    • parse_context函数结束时,self对应的临时对象的生命周期结束,所以Parser::parse()方法返回值的生命周期也结束,无法再作为parse_context函数的返回值

2.1.2 第二步:使用两个生命周期注解

  1. struct Context<'a>(&'a str);
  2. struct Parser<'a,'s>{
  3. context: &'a Context<'s>
  4. }
  5. impl<'a,'s> Parser<'a,'s>{
  6. fn parse(&self) -> Result<(),&str>{
  7. Err(&self.context.0[1..])
  8. }
  9. }
  • 上述代码不能通过编译
    • 结构体Parser使用了两个生命周期注解,但没有指定它们的关系
    • 结构体实例有效期间,其字段必须有效,所以,生命周期's必须不短于'a
    • 然而,代码没有指示's'a的关系

2.1.3 第三步:指示两个生命周期注解的关系

  1. struct Context<'a>(&'a str);
  2. struct Parser<'a,'s:'a>{//使用两个生命周期注解,并且定义s不必a短
  3. context: &'a Context<'s>
  4. }
  5. impl<'a,'s> Parser<'a,'s>{
  6. fn parse(&self) -> Result<(),&'s str>{//必须指定返回值使用哪个生命周期
  7. Err(&self.context.0[1..])
  8. }
  9. }
  • 's:'a表示生命周期's不比'a短,将冒号:理解成>=即可

2.2 对泛型类型的生命周期限定

  1. digraph{
  2. node [margin=0 fontcolor=blue fontsize=16 width=10 shape=box]
  3. a [label="struct Ref<T>(T);"]
  4. b [label="//错误:对于引用类型的结构体字段,必须指定生命周期\nstruct Ref<T>(&T);" fontcolor=red]
  5. c [label="//错误:T可能是引用类型,或者含有引用类型,必须为类型指定生命周期限定\nstruct Ref<'a,T>(&'a T);" fontcolor=red]
  6. d [label="struct Ref<'a,T:'a>(&'a T);"]
  7. a -> b[label="使用引用类型" fontsize="12"]
  8. b -> c[label="为引用类型指定生命周期" fontsize="12"]
  9. c -> d[label="为引用类型的实例&T指定生命周期;同时为类型T指定生命周期限定" fontsize="12"]
  10. }

2.3 特性对象的生命周期推导

  • 特性对象的生命周期默认是'static
  • 为特性指定生命周期的写法:Box<Foo + 'a>,其中Foo是特性类型

3 高级特性

3.1 关联类型

  1. pub trait Iterator {
  2. type Item;
  3. fn next(&mut self) -> Option<Self::Item>;
  4. }
  5. impl Iterator for Counter {
  6. type Item = u32;
  7. fn next(&mut self) -> Option<Self::Item> {
  8. }
  9. }
  10. pub trait Iterator<T> {
  11. fn next(&mut self) -> Option<T>;
  12. }
  • 关联类型与泛型的差别:
    • 使用关联类型时,类型只能实现特性一次,关联到某种特定的类型
    • 使用泛型时,类型可以多次实现特性,每次指定不同的泛型类型参数

3.2 默认泛型类型参数

  • 可通过实现标准库std::ops中的相关特性来实现运算符重载
  • 标准库std::ops中的Add特性定义如下
  1. trait Add<RHS=Self> {
  2. type Output;
  3. fn add(self, rhs: RHS) -> Self::Output;
  4. }
  • 其中的RHS=Self指示了默认的加数类型等于自身,这就是默认泛型类型参数
  • 实现特性的时候,如果不给出泛型类型参数,则使用默认值,例如:
  1. use std::ops::Add;
  2. #[derive(Debug,PartialEq)]
  3. struct Point {
  4. x: i32,
  5. y: i32,
  6. }
  7. impl Add for Point {
  8. type Output = Point;
  9. fn add(self, other: Point) -> Point {
  10. Point {
  11. x: self.x + other.x,
  12. y: self.y + other.y,
  13. }
  14. }
  15. }
  • 实现特性的时候,也可以给出泛型类型参数,以覆盖默认值,例如:
  1. use std::ops::Add;
  2. struct Millimeters(u32);
  3. struct Meters(u32);
  4. impl Add<Meters> for Millimeters {
  5. type Output = Millimeters;
  6. fn add(self, other: Meters) -> Millimeters {
  7. Millimeters(self.0 + (other.0 * 1000))
  8. }
  9. }
  • 默认参数类型主要用于如下两个方面
    1. 扩展类型而不破坏现有代码:通过覆盖泛型类型参数,可以扩展类型以处理新的参数类型,但不影响现有代码
    2. 在大部分用户都不需要的特定情况进行自定义:这是用途1的具体应用

3.3 用完全限定语法消除歧义

3.3.1 特性方法的完全限定语法

  • 不能阻止两个特性含有相同签名的方法;不能阻止类型同时实现这两个特性
  1. trait Pilot {
  2. fn fly(&self);
  3. }
  4. trait Wizard {
  5. fn fly(&self);
  6. }
  7. struct Human;
  8. impl Pilot for Human {
  9. fn fly(&self) {
  10. println!("This is your captain speaking.");
  11. }
  12. }
  13. impl Wizard for Human {
  14. fn fly(&self) {
  15. println!("Up!");
  16. }
  17. }
  18. impl Human {
  19. fn fly(&self) {
  20. println!("*waving arms furiously*");
  21. }
  22. }
  • 上述代码中,类型Human实现了PilotWizard特性,这两个特性都含有fly方法;Human本身也含有fly方法
  • 通过Human实例调用fly方法时,如何指示应该使用哪个fly方法?
  1. fn main() {
  2. let person = Human;
  3. Pilot::fly(&person);//用特性名称限定应该调用哪个方法
  4. Wizard::fly(&person);//用特性名称限定应该调用哪个方法
  5. person.fly();// 也可以写成Human::fly(&person);但这样写复杂一些
  6. }

3.3.2 关联函数的完全限定语法

  1. trait Animal {
  2. fn baby_name() -> String;
  3. }
  4. struct Dog;
  5. impl Dog {
  6. fn baby_name() -> String {
  7. String::from("Spot")
  8. }
  9. }
  10. impl Animal for Dog {
  11. fn baby_name() -> String {
  12. String::from("puppy")
  13. }
  14. }
  15. fn main() {
  16. println!("A baby dog is called a {}", Dog::baby_name());
  17. println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
  18. }

3.3.3 完全限定语法的一般形式

  1. <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 父特性

  1. use std::fmt;
  2. trait OutlinePrint: fmt::Display {
  3. fn outline_print(&self) {
  4. // 调用父特性fmt::Display的to_string()方法
  5. let output = self.to_string();
  6. let len = output.len();
  7. println!("{}", "*".repeat(len + 4));
  8. println!("*{}*", " ".repeat(len + 2));
  9. println!("* {} *", output);
  10. println!("*{}*", " ".repeat(len + 2));
  11. println!("{}", "*".repeat(len + 4));
  12. }
  13. }
  • 上面的例子中,fmt::DisplayOutlinePrint的父特性,为类型实现一个特性时,必须同时实现其父特性
  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. // 这是错误的:Point没有实现OutlinePrint的父特性,所以不能实现子特性
  6. impl OutlinePrint for Point {}
  • 必须为Point类型实现std::fmt::Display,然后才能实现OutlinePrint
  1. use std::fmt;
  2. impl fmt::Display for Point {
  3. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  4. write!(f, "({}, {})", self.x, self.y)
  5. }
  6. }

3.5 使用newtype模式绕过孤儿规则

  • 孤儿规则:特性和类型两者中至少有一个在本地时,才能为类型实现特性,不能为其他包中的类型实现其他包中的特性
  • newtype模式:用元组结构体封装其他包中的类型,然后对这个元组结构体实现其他包中的特性
  1. use std::fmt;
  2. struct Wrapper(Vec<String>);
  3. // 对封装了其他包中类型的元组结构体实现其他包中的特性
  4. impl fmt::Display for Wrapper {
  5. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  6. // 这里用self.0引用被元组结构体封装的Vec<String>类型
  7. write!(f, "[{}]", self.0.join(", "))
  8. }
  9. }
  10. // 实现Deref特性,让类型变成智能指针
  11. impl std::ops::Deref for Wrapper{
  12. type Target = Vec<String>;
  13. fn deref(&self)->&Self::Target{
  14. &self.0
  15. }
  16. }
  17. fn main() {
  18. let w = Wrapper(vec![String::from("hello"), String::from("world")]);
  19. println!("w = {}", w);//这里就可以使用fmt::Display特性了
  20. let x = vec![String::from("a")];
  21. for y in x.iter(){println!("{}",y);}
  22. let w = Wrapper(x);
  23. // 必须实现Deref特性,才可以这样写
  24. // 解引用强制多态:
  25. // 1 类型Wrapper没有iter()方法
  26. // 2 对其调用Deref特性的deref方法,得到&Vec<String>
  27. // 3 &Vec<String>含有iter()方法,使用之
  28. for y in w.iter(){println!("{}",y);}
  29. }

4 高级类型

4.1 类型别名

  1. type Kilometers = i32;
  2. let x: i32 = 5;
  3. let y: Kilometers = 5;
  4. 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:发散函数

  1. fn bar() -> ! {
  2. // --snip--
  3. }

4.2.2 示例2:match中的空类型

  1. fn main(){
  2. let value = None;
  3. let mut guess:u32;
  4. loop{
  5. guess = match value{
  6. Some(v) => v,
  7. None => break,
  8. };
  9. break;
  10. }
  11. println!("{}",guess);
  12. }
  • Ok分支的返回值类型是u32
  • Err分支返回空类型,而空类型!可以强转为其他任何类型
  • 最终match表达式的类型是u32
  • Err分支的break不返回值,guess没有被赋值,控制返回到外层的loop循环语句,循环被中断
  • 流程到达最后的println时,guess可能还没有初始化,所以编译会出错:使用可能未初始化的guess
  • 在声明guess的时候给出初始值就可以通过编译

4.2.3 示例3:用于panic!的空类型

  1. impl<T> Option<T> {
  2. pub fn unwrap(self) -> T {
  3. match self {
  4. Some(val) => val,
  5. None => panic!("called `Option::unwrap()` on a `None` value"),
  6. }
  7. }
  8. }

4.2.4 示例4:loop中的空类型

  1. let x = loop{
  2. break;
  3. };
  4. 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) {}
    • 注意:t的类型从T变成了&TT的大小可能是不确定的;而&T的大小是确定的

      5 高级函数与闭包

      5.1 函数指针

  1. fn add_one(x: i32) -> i32 {
  2. x + 1
  3. }
  4. fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
  5. f(arg) + f(arg)
  6. }
  7. fn twice_do<T>(f: T,arg: i32) -> i32 where T:Fn(i32) -> i32{
  8. f(arg) + f(arg)
  9. }
  10. fn demo1(){
  11. // 函数名作为函数指针使用
  12. let answer = do_twice(add_one, 5);
  13. println!("The answer is: {}", answer);
  14. // 函数名作为Fn特性使用
  15. let answer = twice_do(add_one, 5);
  16. println!("The answer is: {}", answer);
  17. // 闭包作为函数指针使用
  18. let answer = do_twice(|arg: i32|->i32 { arg + 1}, 5);
  19. let answer = do_twice(|x|x+1, 5);
  20. println!("The answer is: {}", answer);
  21. // 闭包作为Fn特性使用
  22. let answer = twice_do(|arg: i32|->i32 { arg + 1}, 5);
  23. let answer = twice_do(|x|x+1, 5);
  24. println!("The answer is: {}", answer);
  25. }
  26. pub fn main() {
  27. demo1();
  28. }
  • 函数名称可以作为函数指针、Fn特性使用
  • 闭包可以作为函数指针、Fn特性使用
  • Rust中,形式参数使用函数指针、Fn系列特性都可以;实际参数使用函数名称、闭包都可以
  • 与外部代码(如C语言)交互时,必须使用函数指针

    5.2 返回闭包

  1. fn returns_closure() -> Fn(i32) -> i32 {
  2. |x|x+1 // 错误:Fn没有实现Sized特性约束
  3. }
  • 闭包不能直接作为Fn系列特性返回:特性是大小不确定类型,不能直接使用
  • 可以用Box封装闭包
  1. fn returns_closure() -> Box<Fn(i32) -> i32> {
  2. Box::new(|x|x+1)
  3. }
  4. println!("The answer is: {}", returns_closure()(5));