Rust模块

使用super起始的相对路径
  我们还可以使用 super 开头来构建从父模块开始的相对路径。这么做类似于文件系统中以.. 开头的语法。我们为什么要这样做呢?
fn serve_order() {} 

mod back_of_house { 
    fn fix_incorrect_order() { 
        cook_order(); 
        super::serve_order();  // 相当于父级目录下调用server_order函数
    }
    fn cook_order() {} 
}

  我们可以使用 super 进入back_of_house 父模块,也就是本例中的 crate 根。在这里,我们可以找到 serve_order 。成功!我们认为 back_of_house 模块和 server_order 函数之间可能具有某种关联关系,并且,如果我们要重新组织这个 crate 的模块树,需要一起移动它们。

  
创建公有的结构体和枚举
  我们还可以使用 pub 来设计公有的结构体和枚举,但是需要注意的是,我们在一个结构体定义的前面使用了pub,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的,我们可以根据情况来决定是否让该字段公有。下面例子我们定义了一个公有结构体 back_of_house:Breakfast ,其中有一个公有字段 toast 和私有字段seasonal_fruit 。这个例子模拟的情况是,在一家餐馆中,顾客可以选择随餐附赠的面包类型,但是厨师会根据季节和库存情况来决定随餐搭配的水果。餐馆可用的水果变化是很快的,所以顾客不能选择水果,甚至无法看到他们将会得到什么水果。
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,  // 公有字段
        seasonal_fruit:String,  // 私有字段
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast:String::from(toast),
                seasonal_fruit:String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");

    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please",meal.toast);

    // The next line won't compile if we uncomment it; we're not allowed 
    // to see or modify the seasonal fruit that comes with the meal 
    
    // meal.seasonal_fruit = String::from("blueberries");  // 编译不通过
}

  因为 back_of_house::Breakfast 结构体的 toast 字段是公有的,所以我们可以在eat_at_restaurant 中使用点号来随意的读写 toast 字段。注意,我们不能在eat_at_restaurant 中使用 seasonal_fruit 字段,因为 seasonal_fruit 是私有的。尝试去除那一行修改 seasonal_fruit 字段值的代码的注释,看看你会得到什么错误!

  还请注意一点,因为 back_of_house::Breakfast 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造示例 Breakfast (这里我们命名为 summer )。如果 Breakfast 没有这样的函数,我们将无法在 eat_at_restaurant 中创建 Breakfast 实例,因为我们不能在eat_at_restaurant 中设置私有字段 seasonal_fruit 的值。

  与之相反,如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 enum关键字前面加上 pub ,就像示例 7-10 展示的那样。
mod back_of_house { 
    pub enum Appetizer { 
        Soup, 
        Salad, 
    } 
}

pub fn eat_at_restaurant() { 
    let order1 = back_of_house::Appetizer::Soup; 
    let order2 = back_of_house::Appetizer::Salad; 
}

  因为我们创建了名为 Appetizer 的公有枚举,所以我们可以在 eat_at_restaurant 中使用Soup 和 Salad 成员。如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 pub 是很令人恼火的,因此枚举成员默认就是公有的。结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub 关键字。

 

使用 use 关键字将名称引入作用域
  在之前我们写的例子中涉及到使用模块时,采用了绝对和相对路径的方式,但是比较麻烦,如果要使用该模块下的多个函数的话,每次都引用显得很麻烦,下面使用use:  
mod front_of_house { 
    pub mod hosting { 
        pub fn add_to_waitlist() {} 
    } 
}
use crate::front_of_house::hosting;  // 从根出发引入模块

pub fn eat_at_restaurant() { 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
}

  通过在 crate 根增加 use crate::front_of_house::hosting ,现在 hosting 在作用域中就是有效的名称了(绝对引用),我们也可以使用相对引用:

mod front_of_house { 
    pub mod hosting { 
        pub fn add_to_waitlist() {} 
    } 
}
use front_of_house::hosting;  // 相对引入模块

pub fn eat_at_restaurant() { 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
}

 

引用起别名

  Rust是不允许同时引入两个相同名字的模块:

use std::fmt; 
use std::io; 
fn function1() -> fmt::Result { 
    // --snip-- # 
    Ok(()) 
}
    
fn function2() -> io::Result<()> { 
    // --snip-- # 
    Ok(()) 
}

//如果我们是指定 use std::fmt::Result 和 use std::io::Result ,
//我们将在同一作用域拥有了两个 Result 类型, 当我们使用 Result 时,Rust 则不知道我们要用的是哪个。
  使用 use 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 as 指定一个新的本地名称或者别名。
use std::fmt::Result; 
use std::io::Result as IoResult; 
fn function1() -> Result { 
    // --snip-- # 
    Ok(()) 
}
    
fn function2() -> IoResult<()> { 
    // --snip-- # 
    Ok(()) 
}

  

使用 pub use 重导出名称
  可以结合 pub 和 use 。这个技术被称为 “重导出(re-exporting)”,因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。
mod front_of_house { 
    pub mod hosting { 
        pub fn add_to_waitlist() {} 
    } 
}
pub use crate::front_of_house::hosting;  //  pub use 使名称可引入任何代码的作用域中 重导出

pub fn eat_at_restaurant() { 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
}

  

使用外部包

  我们之前使用了rand依赖包,首先在 Cargo.toml 中加入了如下行:

[dependencies] 
rand = "0.5.5"

  接着,为了将 rand 定义引入项目包的作用域,我们加入一行 use 起始的包名,它以rand 包名开头并列出了需要引入作用域的项。注意标准库( std )对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std ,不过需要通过 use 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 HashMap :

use std::collections::HashMap;

  如果是引入第三方包:

use rand::Rng;

  

嵌套路径来消除大量的 use 行

  当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间。

use std::cmp::Ordering; 
use std::io; 
// ---snip---

// 修改为
use std::{cmp::Ordering, io};

  我们可以在路径的任何层级使用嵌套路径,这在组合两个共享子路径的 use 语句时非常有用:

use std::io; 
use std::io::Write;


// 修改为
use std::io::{self, Write};  // 可以在嵌套路径中使用 self

  

通过 glob 运算符将所有的公有定义引入作用域
  如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 * ,glob 运算符:
use std::collections::*;

  

将模块分割进不同文件
  首先列出要分割的代码,src/lib.rs:
mod front_of_house { 
    pub mod hosting { 
        pub fn add_to_waitlist() {} 
    } 
}
pub use crate::front_of_house::hosting;  //  pub use 使名称可引入任何代码的作用域中重导出

pub fn eat_at_restaurant() { 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
}

  下面是我们分割后的代码,src下新建了一个front_of_house.rs文件,以及原有的lib.rs文件,首先看看lib.rs代码:

mod front_of_house;  //  mod关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。

pub use crate::front_of_house::hosting;  //  pub use 使名称可引入任何代码的作用域中 重导出

pub fn eat_at_restaurant() { 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
    hosting::add_to_waitlist(); 
}

  front_of_house.rs文件代码:

pub mod hosting { 
    pub fn add_to_waitlist() {} 
}

  

 
posted @ 2020-06-23 15:12  独角兕大王  阅读(424)  评论(0编辑  收藏  举报