Crate和模块

每次你在 Rust 中写代码时,你都是在 crate 中写的。crate是一个或多个文件,一起为你的代码服务。在你写的文件里面,你也可以创建一个modmod是存放函数、结构体等的空间,因为这些原因被使用:

  • 构建你的代码:它可以帮助你思考代码的总体结构。当你的代码越来越大时,这一点可能很重要。
  • 阅读你的代码:人们可以更容易理解你的代码。例如,std::collections::HashMap这个名字告诉你,它在std的模块collections里面。这给了你一个提示,也许collections里面还有更多的集合类型,你可以尝试一下。
  • 私密性:所有的东西一开始都是私有的。这样可以让你不让用户直接使用函数。

要创建一个mod,只需要写mod,然后用{}开始一个代码块。我们将创建一个名为print_things的mod,它有一些打印相关的功能。

mod print_things {
    use std::fmt::Display;

    fn prints_one_thing<T: Display>(input: T) { // Print anything that implements Display
        println!("{}", input)
    }
}

fn main() {}

你可以看到,我们把use std::fmt::Display;写在print_things里面,因为它是一个独立的空间。如果你把use std::fmt::Display;写在main()里面,那没用。而且,我们现在也不能从main()里面调用。如果在fn前面没有pub这个关键字,它就会保持私密性。让我们试着在没有pub的情况下调用它。这里有一种写法。

// 🚧
fn main() {
    crate::print_things::prints_one_thing(6);
}

crate的意思是 "在这个项目里",但对于我们简单的例子来说,它和 "在这个文件里面"是一样的。接着是print_things这个mod,最后是prints_one_thing()函数。你可以每次都写这个,也可以写use来导入。现在我们可以看到说它是私有的错误:

// ⚠️
mod print_things {
    use std::fmt::Display;

    fn prints_one_thing<T: Display>(input: T) {
        println!("{}", input)
    }
}

fn main() {
    use crate::print_things::prints_one_thing;

    prints_one_thing(6);
    prints_one_thing("Trying to print a string...".to_string());
}

这是错误的。

error[E0603]: function `prints_one_thing` is private
  --> src\main.rs:10:30
   |
10 |     use crate::print_things::prints_one_thing;
   |                              ^^^^^^^^^^^^^^^^ private function
   |
note: the function `prints_one_thing` is defined here
  --> src\main.rs:4:5
   |
4  |     fn prints_one_thing<T: Display>(input: T) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

很容易理解,函数print_one_thing是私有的。它还用src\main.rs:4:5告诉我们在哪里可以找到这个函数。这很有帮助,因为你不仅可以在一个文件中写mod,还可以在很多文件中写mod

现在我们只需要写pub fn而不是fn,一切就都可以了。

mod print_things {
    use std::fmt::Display;

    pub fn prints_one_thing<T: Display>(input: T) {
        println!("{}", input)
    }
}

fn main() {
    use crate::print_things::prints_one_thing;

    prints_one_thing(6);
    prints_one_thing("Trying to print a string...".to_string());
}

这个打印:

6
Trying to print a string...

pub对结构体、枚举、trait或模块有什么作用?pub对它们来说是这样的:

  • pub对于一个结构:它使结构公开,但成员不是公开的。要想让一个成员公开,你也要为每个成员写pub
  • pub 对于一个枚举或trait:所有的东西都变成了公共的。这是有意义的,因为traits是给事物赋予相同的行为。而枚举是值之间的选择,你需要看到所有的枚举值才能做选择。
  • pub对于一个模块来说:一个顶层的模块会是pub,因为如果它不是pub,那么根本没有人可以使用里面的任何东西。但是模块里面的模块需要使用pub才能成为公共的。

我们在print_things里面放一个名为Billy的结构体。这个结构体几乎都会是public的,但也不尽然。这个结构是公共的,所以它这样写:pub struct Billy。里面会有一个 nametimes_to_printname不会是公共的,因为我们只想让用户创建名为"Billy".to_string()的结构。但是用户可以选择打印的次数,所以这将是公开的。它的是这样的:

mod print_things {
    use std::fmt::{Display, Debug};

    #[derive(Debug)]
    pub struct Billy { // Billy is public
        name: String, // but name is private.
        pub times_to_print: u32,
    }

    impl Billy {
        pub fn new(times_to_print: u32) -> Self { // That means the user needs to use new to create a Billy. The user can only change the number of times_to_print
            Self {
                name: "Billy".to_string(), // We choose the name - the user can't
                times_to_print,
            }
        }

        pub fn print_billy(&self) { // This function prints a Billy
            for _ in 0..self.times_to_print {
                println!("{:?}", self.name);
            }
        }
    }

    pub fn prints_one_thing<T: Display>(input: T) {
        println!("{}", input)
    }
}

fn main() {
    use crate::print_things::*; // Now we use *. This imports everything from print_things

    let my_billy = Billy::new(3);
    my_billy.print_billy();
}

这将打印:

"Billy"
"Billy"
"Billy"

对了,导入一切的*叫做 "glob运算符"。Glob的意思是 "全局",所以它意味着一切。

mod里面你可以创建其他mod。一个子 mod(mod里的mod)总是可以使用父 mod 内部的任何东西。你可以在下一个例子中看到这一点,我们在 mod province 里面有一个 mod city,而mod provincemod country 里面。

你可以这样想:即使你在一个国家,你可能不在一个省。而即使你在一个省,你也可能不在一个市。但如果你在一个城市,你就在这个城市的省份和它的国家。

mod country { // The main mod doesn't need pub
    fn print_country(country: &str) { // Note: this function isn't public
        println!("We are in the country of {}", country);
    }
    pub mod province { // Make this mod public

        fn print_province(province: &str) { // Note: this function isn't public
            println!("in the province of {}", province);
        }

        pub mod city { // Make this mod public
            pub fn print_city(country: &str, province: &str, city: &str) {  // This function is public though
                crate::country::print_country(country);
                crate::country::province::print_province(province);
                println!("in the city of {}", city);
            }
        }
    }
}

fn main() {
    crate::country::province::city::print_city("Canada", "New Brunswick", "Moncton");
}

有趣的是,print_city可以访问print_provinceprint_country。这是因为mod city在其他mod里面。它不需要在print_province前面添加pub之后才能使用。这也是有道理的:一个城市不需要做什么,它本来就在一个省里,在一个国家里。

你可能注意到,crate::country::province::print_province(province);非常长。当我们在一个模块里面的时候,我们可以用super从上面引入元素。其实super这个词本身就是"上面"的意思,比如 "上级"。在我们的例子中,我们只用了一次函数,但是如果你用的比较多的话,那么最好是导入。如果它能让你的代码更容易阅读,那也是个好主意,即使你只用了一次函数。现在的代码几乎是一样的,但更容易阅读一些。

mod country {
    fn print_country(country: &str) {
        println!("We are in the country of {}", country);
    }
    pub mod province {
        fn print_province(province: &str) {
            println!("in the province of {}", province);
        }

        pub mod city {
            use super::super::*; // use everything in "above above": that means mod country
            use super::*;        // use everything in "above": that means mod province

            pub fn print_city(country: &str, province: &str, city: &str) {
                print_country(country);
                print_province(province);
                println!("in the city of {}", city);
            }
        }
    }
}

fn main() {
    use crate::country::province::city::print_city; // bring in the function

    print_city("Canada", "New Brunswick", "Moncton");
    print_city("Korea", "Gyeonggi-do", "Gwangju"); // Now it's less work to use it again
}