Skip to main content
  1. Tutorial/

Rust Book Datastructure - mod

879 words·5 mins
Rust
Table of Contents

特征对象
#

  • draw1 函数的参数是Box<dyn Draw> 形式的特征对象,该特征对象是通过 Box::new(x) 的方式创建的
  • draw2 函数的参数是 ``&dyn Draw形式的特征对象,该特征对象是通过&x 的方式创建的dyn`
  • 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn
trait Draw {
    fn draw(&self) -> String;
}
impl Draw for u8 {
    fn draw(&self) -> String {
        format!("u8: {}", *self)
    }
}
impl Draw for f64 {
    fn draw(&self) -> String {
        format!("f64: {}", *self)
    }
}
// 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box<T> 可以被隐式转换成函数参数签名中的Box<dyn Draw>
fn draw1(x: Box<dyn Draw>) {
    // 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法
    x.draw();
}
fn draw2(x: &dyn Draw) {
    x.draw();
}
fn main() {
    let x = 1.1f64;
    // do_something(&x);
    let y = 8u8;
    // x 和 y 的类型 T 都实现了 `Draw` 特征,因为 Box<T> 可以在函数调用时隐式地被转换为特征对象 Box<dyn Draw>
    // 基于 x 的值创建一个 Box<f64> 类型的智能指针,指针指向的数据被放置在了堆上
    draw1(Box::new(x));
    // 基于 y 的值创建一个 Box<u8> 类型的智能指针
    draw1(Box::new(y));
    draw2(&x);
    draw2(&y);
}

动态分发
#

  • 泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是静态分发(static dispatch),因为是在编译期完成的,对于运行期性能完全没有任何影响。
  • 与静态分发相对应的是动态分发(dynamic dispatch),在这种情况下,直到运行时,才能确定需要调用什么方法。之前代码中的关键字 dyn 正是在强调这一“动态”的特点。
  • 当使用特征对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用特征对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。

静态分发 Box<T> 和动态分发 Box<dyn Trait> 的区别

  • 特征对象大小不固定:这是因为,对于特征 Draw ,类型 Button 可以实现特征 Draw ,类型SelectBox 也可以实现特征 Draw ,因此特征没有固定大小

  • 几乎总是使用特征对象的引用方式,如 &dyn Draw 、 Box<dyn Draw>

    • 虽然特征对象没有固定大小,但它的引用类型的大小是固定的,它由两个指针组成( ptr和 vptr ),因此占用两个指针大小
    • 一个指针 ptr 指向实现了特征 Draw 的具体类型的实例,也就是当作特征 Draw 来用的类型的实例,比如类型 Button 的实例、类型 SelectBox 的实例
    • 另一个指针 vptr 指向一个虚表 vtable , vtable 中保存了类型 Button 或类型SelectBox 的实例对于可以调用的实现于特征 Draw 的方法。

类型 Button 实现了特征 Draw 时,类型 Button 的实例对象 btn 可以当作特征Draw 的特征对象类型来使用, btn 中保存了作为特征对象的数据指针(指向类型 Button 的实例数据)和行为指针(指向 vtable )。

此时的 btn 是 Draw 的特征对象的实例,而不再是具体类型 Button 的实例,而且btn 的 vtable 只包含了实现自特征 Draw 的那些方法(比如 draw ),因此 btn 只能调用实现于

特征 Draw 的 draw 方法,而不能调用类型 Button 本身实现的方法和类型 Button 实现于其他特征的方法。也就是说, btn 是哪个特征对象的实例,它的 vtable 中就包含了该特征的方法

在外部类型上实现外部特征(newtype)
#

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);
}

Vector 存储
#

trait IpAddr {
    fn display(&self);
}
struct V4(String);
impl IpAddr for V4 {
    fn display(&self) {
        println!("ipv4: {:?}", self.0)
    }
}
struct V6(String);
impl IpAddr for V6 {
    fn display(&self) {
        println!("ipv6: {:?}", self.0)
    }
}
fn main() {
    //  Vec<Box<dynIpAddr>> ,表示数组 v 存储的是特征 IpAddr 的对象,这样就实现了在数组中存储不同的类型
    let v: Vec<Box<dyn IpAddr>> = vec![
        Box::new(V4("127.0.0.1".to_string())),
        Box::new(V6("::1".to_string())),
    ];
    for ip in v {
        ip.display();
    }
}

Vec 不稳定排序
#

// #[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] 可以不适用闭包
fn main() {
    let mut vec = vec![1.0, 5.6, 10.3, 2.0, 15f32];
    // vec.sort_unstable();
    // 在浮点数当中,存在一个 NAN 的值,这个值无法与其他的浮点数进行对比
    // 因此,浮点数类型并没有实现全数值可比较 Ord 的特性,而是实现了部分可比较的特性 PartialOrd
    // 使用 partial_cmp来作为大小判断的依据。
    vec.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
    assert_eq!(vec, vec![1.0, 2.0, 5.6, 10.3, 15f32]);
}

error[E0277]: the trait bound `f32: Ord` is not satisfied
    --> exercises/intro/intro.rs:3:9
     |
3    |     vec.sort_unstable();
     |         ^^^^^^^^^^^^^ the trait `Ord` is not implemented for `f32`
     |
     = help: the following other types implement trait `Ord`:
               isize
               i8
               i16
               i32
               i64
               i128
               usize
               u8
             and 4 others
note: required by a bound in `core::slice::<impl [T]>::sort_unstable`

悬垂指针和生命周期
#

生命周期的主要作用是避免悬垂引用,它会导致程序引用了本不该引用的数据

fn main() {
    {
	
        let r;
        {
            let x = 5;
            r = &x;
        }
        println!("r: {}", r);
    }
}

error[E0597]: `x` does not live long enough
 --> exercises/intro/intro.rs:6:17
  |
5 |             let x = 5;
  |                 - binding `x` declared here
6 |             r = &x;
  |                 ^^ borrowed value does not live long enough
7 |         }
  |         - `x` dropped here while still borrowed
8 |         println!("r: {}", r);
  |                           - borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

生命周期标注并不会改变任何引用的实际作用域

&i32 // 一个引用
&'a i32 // 具有显式生命周期的引用
&'a mut i32 // 具有显式生命周期的可变引用

结构体中的生命周期
#

struct ImportantExcerpt<'a> {
    part: &'a str,
}
fn main() {
    let novel: String = String::from("Call me Ishmael. Some years ago...");
    let first_sentence: &str = novel.split('.').next().expect("Could not find a '.'");
    let i: ImportantExcerpt<'_> = ImportantExcerpt {
        part: first_sentence,
    };
}
struct ImportantExcerpt<'a> {
    part: &'a str,
}
fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    print!("{}", i.announce_and_return_part("Hello"));
}
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str
    where
        'a: 'b,
    {
        println!("Attention please: {}", announcement);
        self.part
    }
}

返回值和错误处理
#

use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("info.toml")?;
    Ok(())
}
// 用 ? 提前返回
// std::error:Error 是 Rust 中抽象层次最高的错误,其它标准库中的错误都实现了该特征
// 可以用该特征对象代表一切错误,就算 main 函数中调用任何标准库函数发生错误,都可以通过
// Box<dyn Error> 这个特征对象进行返回

模块使用及处理
#

// 一个名为 `my_mod` 的模块
mod my_mod {
    // 模块中的项默认具有私有的可见性
    fn private_function() {
        println!("called `my_mod::private_function()`");
    }
    // 使用 `pub` 修饰语来改变默认可见性。
    pub fn function() {
        println!("called `my_mod::function()`");
    }
    // 在同一模块中,项可以访问其它项,即使它是私有的。
    pub fn indirect_access() {
        print!("called `my_mod::indirect_access()`, that\n> ");
        private_function();
    }
    // 模块也可以嵌套
    pub mod nested {
        pub fn function() {
            println!("called `my_mod::nested::function()`");
        }
        #[allow(dead_code)]
        fn private_function() {
            println!("called `my_mod::nested::private_function()`");
        }
        // 使用 `pub(in path)` 语法定义的函数只在给定的路径中可见。
        // `path` 必须是父模块(parent module)或祖先模块(ancestor module)
        pub(in crate::my_mod) fn public_function_in_my_mod() {
            print!(
                "called `my_mod::nested::public_function_in_my_mod()`, that\n >
"
            );
            public_function_in_nested()
        }
        // 使用 `pub(self)` 语法定义的函数则只在当前模块中可见。
        pub(self) fn public_function_in_nested() {
            println!("called `my_mod::nested::public_function_in_nested");
        }
        // 使用 `pub(super)` 语法定义的函数只在父模块中可见。
        pub(super) fn public_function_in_super_mod() {
            println!("called my_mod::nested::public_function_in_super_mod");
        }
    }
    pub fn call_public_function_in_my_mod() {
        print!("called `my_mod::call_public_funcion_in_my_mod()`, that\n> ");
        nested::public_function_in_my_mod();
        print!("> ");
        nested::public_function_in_super_mod();
    }
    // `pub(crate)` 使得函数只在当前包中可见
    pub(crate) fn public_function_in_crate() {
        println!("called `my_mod::public_function_in_crate()");
    }
    // 嵌套模块的可见性遵循相同的规则
    mod private_nested {
        #[allow(dead_code)]
        pub fn function() {
            println!("called `my_mod::private_nested::function()`");
        }
    }
}
fn function() {
    println!("called `function()`");
}
fn main() {
    // 模块机制消除了相同名字的项之间的歧义。
    function();
    my_mod::function();
    // 公有项,包括嵌套模块内的,都可以在父模块外部访问。
    my_mod::indirect_access();
    my_mod::nested::function();
    my_mod::call_public_function_in_my_mod();
    // pub(crate) 项可以在同一个 crate 中的任何地方访问
    my_mod::public_function_in_crate();
    // pub(in path) 项只能在指定的模块中访问
    // 报错!函数 `public_function_in_my_mod` 是私有的
    //my_mod::nested::public_function_in_my_mod();

    // 模块的私有项不能直接访问,即便它是嵌套在公有模块内部的
    // 报错!`private_function` 是私有的
    //my_mod::private_function();

    // 报错!`private_function` 是私有的
    //my_mod::nested::private_function();

    // 报错! `private_nested` 是私有的
    // my_mod::private_nested::function();
}

Related

Rust Book Datastructure - Trait
998 words·5 mins
Rust
结构体泛型与函数泛型 # struct Point<T, U> { x: T, y: U, } impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.
Rust Book Datastructure - Micro
1076 words·6 mins
Rust
结构体 # struct Name{ name: String, } 初始化实例时,每个字段都需要进行初始化 初始化时的字段顺序不需要和结构体定义时的顺序一致 3.
Rust Book Datastructure - Datastructure
1485 words·7 mins
Rust
Rust DataStucture # // 类型推导与标注 let i = "42".
Install Rust
477 words·3 mins
Rust
Macos # https://www.rust-lang.org/tools/install Centos 7 # Note: Before installing Rust, make sure that the system has installed C and C++ compilers, as well as other necessary dependencies.
Rust 宏使用方法以及属性说明
468 words·3 mins
Rust
#[derive(Debug)] 是一个 Rust 内置的属性,用于为结构体或枚举类型自动生成 Debug trait 的实现。