% Rust的Crates和Modules
当一个项目越来越庞大、代码越来越多的时候,一个好的工程实施方式是将这些代码划分为一个部分一个部分,分而治之,并且采用private和public限定方式,定义好各部分之间的接口。为了方便这种工程实施方式,Rust中采用了模块的代码组织方式。
基本概念:Crates和Modules
Rust有两个不同的模块组织方式:Crate和Module。一个Crate相当于其它语言的library和package。Rust中采用Cargo这个包管理工具,来管理一个一个的Crate。一个Crate能够被生成为一个可执行文件或共享库。
每个Crate都有一个隐含的根模块文件,其它的模块就引用在这个根模块文件下,这其中关键的手段就是采用Module。
下面讲一个例子,我们将生成一个名为”phrases”的Crate,这个Crate包含了不同语言的短语,为了使例子简单,仅区分了英语和中文的”greetings”和”farewells”。该例子的模块设计如下:
+-----------++---| greetings || +-----------++---------+ || english |---++---------+ | +-----------+| +---| farewells |+---------+ | +-----------+| phrases |---++---------+ | +-----------+| +---| greetings |+----------+ | +-----------+| chinese |---++----------+ || +-----------++---| farewells |+-----------+
在这个例子中,”phrases”是这个Crate的名子,其它的就作为Module,从图中可以看出,这个Crate的组织方式就像一个树型结构,”phrases”是这个Crate的根,”english”和”chinese”两个Module是这个Crate的分枝。
下面用代码来实现这个Crate:
$ cargo new phrases //这是在创建一个library$ cd phrases$ tree.├── Cargo.toml└── src└── lib.rs1 directory, 2 files
src/lib.rs就是”phrases”这个Crate的根。
定义Modules
我们将使用mod这个关键定来定义各个模块,src/lib.rs里的代码如下:
mod english {mod greetings {}mod farewells {}}mod chinese {mod greetings {}mod farewells {}}
上面的代码中可见,模块的定义是mod后紧跟模块名,模块内部代码包含在{}之间。在一个已知的mod中,可以定义下级mod,并通过::符号引用下级mod。在本例中的四个嵌套模块引用方式如下:english::greetings、english::farewells、chinese::greetings和chinese::farewells,这里的greetings和farewells分别分布在两个不同的模块中,所以并没有命名冲突。
由于这个Crate没有定义main()函数,所以用Cargo工具只能生成一个库文件:
$ cargo buildCompiling phrases v0.0.1 (file:///%E6%A1%8C%E9%9D%A2/phrases)$ ls target/deps examples libphrases-066b8293cb7b2145.rlib native
libphrases-066b8293cb7b2145.rlib就是生成后的库文件。
分割为多个文件组成的Crate
如果每个Crate仅包含一个文件,一旦工程代码增加,这个Crate将变得很大,而难以维护。通常都是将这个Crate分割成多个文件,Rust支持如下文件组织方式:
结合前面的”phrases”的描述,如果要声明一个english的下级模块,我们可以在Cargo生成的项目代码中创建src/english.rs或src/english/mod.rs代码文件,并在src/lib.rs文件中写入的如下代码:
mod english;
这样就可以引用english模块。
其项目目标文件构成如下(注:在src/english/的下级文件中并没有写入代码):
$ tree.├── Cargo.lock├── Cargo.toml├── src│ ├── english│ │ ├── farewells.rs│ │ ├── greetings.rs│ │ └── mod.rs│ ├── chinese│ │ ├── farewells.rs│ │ ├── greetings.rs│ │ └── mod.rs│ └── lib.rs└── target├── deps├── examples├── libphrases-066b8293cb7b2145.rlib└── native7 directories, 10 files
其中src/lib.rs就是”phrases”这个Crate的根,其引用下级模块的代码如下:
mod english;mod chinese;
这样的声明,告诉Rust自动寻找src/english.rs、src/chinese.rs或者src/english/mod.rs、src/chinese/mod.rs。同样,在english或chinese这一级模块中要引用下级模块就可以分别在src/english/mod.rs和src/chinese/mod.rs进行声明:
mod greetings;mod farewells;
同样,这个声明将告诉Rust自动寻找src/english/greetings.rs、src/chinese/greetings.rs或者src/english/farewells/mod.rs、src/chinese/farewells/mod.rs。
截止目前,src/english和src/chinese下所有文件都是空的。下面,我们将在相应的文件中定义一些函数。
在src/english/greetings.rs输入如下:
fn hello() -> String {"hello!".to_string()}
在src/english/farewells.rs输入如下:
fn goodbye() -> String {"goodbye!".to_string()}
在src/chinese/greetings.rs输入如下:
fn hello() -> String {"你好".to_string()}
在src/chinese/farewells.rs输入如下:
fn goodbye() -> String {"再见".to_string()}
下面,让我们来进行模块之间的相互调用(仅定了函数,而没有引用,在运行cargo build的时候,Rust会自动报错误)。
引入Crates定义的库
前面已经定义了一个名为”phrases”的Crate,如何使用这个库呢?
下面将创建一个src/main.rs文件,并输入如下代码:
extern crate phrases;fn main() {println!("Hello in English: {}", phrases::english::greetings::hello());println!("Goodbye in English: {}", phrases::english::farewells::goodbye());println!("Hello in Chinese: {}", phrases::chinese::greetings::hello());println!("Goodbye in Chinese: {}", phrases::chinese::farewells::goodbye());}
extern crate声明编译时将链接”phrases”这个Crate,这样才能通过::逐级引用”phrases”里面定义的函数。
同时,Cargo也认为src/main.rs也是一个Crate的根,一旦编译src/main.rs成功将获得一个可执行文件。目前,我们的项目包含了src/lib.rs和src/main.rs这两个Crate,这种组织方式对于生成可执行的Crate非常普遍,很多时候将函数定义在库Crate中,生成可执行的Crate调用这个库。
下面我们来编译这个项目,但会报错如下:
$ cargo buildCompiling phrases v0.0.1 (file:///%E6%A1%8C%E9%9D%A2/phrases)/phrases/src/english/greetings.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default/phrases/src/english/greetings.rs:1 pub fn hello() -> String {/phrases/src/english/greetings.rs:2 "hello!".to_string()/phrases/src/english/greetings.rs:3 }/phrases/src/english/farewells.rs:1:1: 3:2 warning: function is never used: `goodbye`, #[warn(dead_code)] on by default/phrases/src/english/farewells.rs:1 pub fn goodbye() -> String {/phrases/src/english/farewells.rs:2 "goodbye".to_string()/phrases/src/english/farewells.rs:3 }/phrases/src/chinese/greetings.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default/phrases/src/chinese/greetings.rs:1 fn hello() -> String {/phrases/src/chinese/greetings.rs:2 "你好".to_string()/phrases/src/chinese/greetings.rs:3 }/phrases/src/chinese/farewells.rs:1:1: 3:2 warning: function is never used: `goodbye`, #[warn(dead_code)] on by default/phrases/src/chinese/farewells.rs:1 fn goodbye() -> String {/phrases/src/chinese/farewells.rs:2 "再见".to_string()/phrases/src/chinese/farewells.rs:3 }/phrases/src/main.rs:4:38: 4:72 error: function `hello` is private/phrases/src/main.rs:4 println!("Hello in English: {}", phrases::english::greetings::hello());^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~note: in expansion of format_args!<std macros>:2:23: 2:77 note: expansion site<std macros>:1:1: 3:2 note: in expansion of println!/phrases/src/main.rs:4:5: 4:76 note: expansion site/phrases/src/main.rs:5:40: 5:76 error: function `goodbye` is private/phrases/src/main.rs:5 println!("Goodbye in English: {}", phrases::english::farewells::goodbye());^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~note: in expansion of format_args!<std macros>:2:23: 2:77 note: expansion site<std macros>:1:1: 3:2 note: in expansion of println!/phrases/src/main.rs:5:5: 5:80 note: expansion site/phrases/src/main.rs:6:39: 6:74 error: function `hello` is private/phrases/src/main.rs:6 println!("Hello in Chinese: {}", phrases::chinese::greetings::hello());^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~note: in expansion of format_args!<std macros>:2:23: 2:77 note: expansion site<std macros>:1:1: 3:2 note: in expansion of println!/phrases/src/main.rs:6:5: 6:78 note: expansion site/phrases/src/main.rs:7:41: 7:78 error: function `goodbye` is private/phrases/src/main.rs:7 println!("Goodbye in Chinese: {}", phrases::chinese::farewells::goodbye());^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~note: in expansion of format_args!<std macros>:2:23: 2:77 note: expansion site<std macros>:1:1: 3:2 note: in expansion of println!/phrases/src/main.rs:7:5: 7:82 note: expansion siteerror: aborting due to 4 previous errorsCould not compile `phrases`.To learn more, run the command again with --verbose.
通过报错的信息可见,在Rust所有的模块和函数默认情况下都是私有的。
定义公共接口
虽然默认情况下,Rust定义的函数、模块和变量都是私有的,但Rust提供了非常精细地定义公共接口的方法。一般使用pub这个关键字来定义公共接口。为了上面的例子能够正常运行,我们将在相应的模块和函数处添加pub关键字:
src/lib.rs代码修改如下:
pub mod english;pub mod chinese;
src/english/mod.rs、src/chinese/mod.rs代码修改如下:
pub mod greetings;pub mod farewells;
src/english/greetings.rs代码修改如下:
pub fn hello() -> String {"hello!".to_string()}
src/english/farewells.rs代码修改如下:
pub fn goodbye() -> String {"goodbye!".to_string()}
src/chinese/greetings.rs代码修改如下:
pub fn hello() -> String {"你好".to_string()}
src/chinese/farewells.rs代码修改如下:
pub fn goodbye() -> String {"再见".to_string()}
运行cargo build将会执行成功。
前面,在src/main.rs引用下级模块的函数时,输入了phrases::english::greetings::hello()这个太长,并重复输入了4次,不简洁。Rust提供了另一个关键字use来定义当前可见泛围。
使用use引入Modules
Rust提供了use关键字能够将下级模块的可见泛围引入到当前,下面将src/main.rs里的代码修改如下:
extern crate phrases;use phrases::english::greetings;use phrases::english::farewells;fn main() {println!("Hello in English: {}", greetings::hello());println!("Goodbye in English: {}", farewells::goodbye());}
带有use关键字的两行代码,将greetings和farewells这两个模块的可见泛围提到了当前,所以,我们可以直接使用这个两个模块下面定义的函数。最佳实践就是这样,直接引入下级模块,而不是直接引入函数。
像下面这样直接引入函数:
extern crate phrases;use phrases::english::greetings::hello;use phrases::english::farewells::goodbye;fn main() {println!("Hello in English: {}", hello());println!("Goodbye in English: {}", goodbye());}
这样做在上面这个很简单的项目中,Rust不会报错,一旦项目变大,将很容易存在函数命名冲突。比如像下面这样:
extern crate phrases;use phrases::english::greetings::hello;use phrases::chinese::greetings::hello;fn main() {println!("Hello in English: {}", hello());println!("Goodbye in Chinese: {}", hello());}
则会报错如下:
$ cargo buildCompiling phrases v0.0.1 (file:///%E6%A1%8C%E9%9D%A2/phrases)/phrases/src/main.rs:5:5: 5:40 error: a value named `hello` has already been imported in this module/phrases/src/main.rs:5 use phrases::chinese::greetings::hello;^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~error: aborting due to previous errorCould not compile `phrases`.To learn more, run the command again with --verbose.
如果我们将引入同一个模块的多个不同的下级模块,我们可以采用更加简便的代码输入方式:
use phrases::english::{greetings, farewells};
使用pub use在内部Module引入其它模块
我们可以在已定义Crate内部的其它模块中,引入函数,这样可以在既定的内部代码组织结构中添加附加接口。
下面,接合前面的例子,修改src/main.rs如下:
extern crate phrases;use phrases::english::{greetings, farewells};use phrases::chinese;fn main() {println!("Hello in English: {}", greetings::hello());println!("Goodbye in English: {}", farewells::goodbye());println!("Hello in Chinese: {}", chinese::hello());println!("Goodbye in Chinese: {}", chinese::goodbye());}
再修改src/chinese/mod.rs如下:
pub use self::greetings::hello;pub use self::farewells::goodbye;mod greetings;mod farewells;
pub use声明,将chinese模块的下级模块的函数可见泛围引入到当前模块,并成为当前chinese模块的一个函数。
同时,要注意pub use或者use是在mod声明之前定义的。
编译、运行,其结果如下:
$ cargo buildCompiling phrases v0.0.1 (file:///%E6%A1%8C%E9%9D%A2/phrases)$ ./target/phrasesHello in English: hello!Goodbye in English: goodbyeHello in Chinese: 你好Goodbye in Chinese: 再见
