刚接触Rust遇到一堆新概念,特别是package, crate, mod 这些,特别迷糊,记录一下
一、pakcage与crate
当我们用cargo 创建一个新项目时,默认就创建了一个package,参考下面的截图:
这样就生成了一个名为demo_1的package,另外也创建1个所谓的binary crate,当然也可以加参数 --lib生成library的crate
然后在crate里,又可以创建一堆所谓的mod(模块),因此整体的关系,大致象下面这张图:
即:
- 1个Package里,至少要有1种Crate(要么是Library Crate,要么是Binary Crate)
- 1个Package里,最多只能有1个Library Crate
- 1个Package里,可以有0或多个Binary Crate
- 1个Crate里,可以创建0或多个mod(后面还会详细讲mod)
二、crate的入口
通常在创建项目后,会默认生成src/main.rs,里面有1个main方法:
(base) ➜ code tree demo_1 demo_1 ├── Cargo.toml └── src └── main.rs
main.rs的内容:
fn main() { println!("Hello, world!"); }
这个就是crate运行时的入口函数,前面我们提过,1个package里,允许有1个library crate和多个binary crate,我们弄个复杂点的场景:
(base) ➜ demo_1 git:(master) ✗ tree . ├── Cargo.lock ├── Cargo.toml └── src ├── lib.rs ├── main.rs └── main2.rs
在src里,再加2个文件lib.rs及main2.rs,内容如下:
lib.rs
pub fn foo(){ println!("foo in lib"); }
main2.rs
fn main(){ demo_1::foo(); println!("hello 2"); }
同时把main.rs里也加一行demo_1::foo(),让它调用lib.rs里的foo()方法
fn main() { demo_1::foo(); println!("Hello, world!"); }
看上去,我们有2个main入口函数了,运行一下看看结果如何:
(base) ➜ demo_1 git:(master) ✗ cargo run Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1) Finished dev [unoptimized + debuginfo] target(s) in 0.70s Running `target/debug/demo_1` foo in lib Hello, world!
从最后2行的输出来看,运行的是main.rs中的方法,即:main2.rs中的main函数,并未识别成入口,继续折腾,在src下创建目录bin,然后把main.rs以及main2.rs都移动到bin目录
(base) ➜ demo_1 git:(master) ✗ tree . ├── Cargo.lock ├── Cargo.toml └── src ├── bin │ ├── main.rs │ └── main2.rs └── lib.rs
然后再运行:
(base) ➜ demo_1 git:(master) ✗ cargo run error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key. available binaries: main, main2
这次提示不一样了,大意是说有2个入口main, main2,不知道该运行哪一个,需要加参数明确告诉cargo,加1个参数 --bin main2
(base) ➜ demo_1 git:(master) ✗ cargo run --bin main2 Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1) Finished dev [unoptimized + debuginfo] target(s) in 0.62s Running `target/debug/main2` foo in lib hello 2
这样就可以了
三、 mod
3.1 定义mod
把main.rs里加点代码:
mod a { pub fn foo_a_1() { println!("foo_a_1"); } fn foo_a_2(){ println!("foo_a_2"); } mod b { pub fn foo_b() { foo_a_2(); println!("foo_b"); } } } fn main() { a::foo_a_1(); a::foo_a_2(); a::b::foo_b(); }
解释一下:
- 用mod关键字,定义了模块a,然后里面还嵌套了模块b
- 然后在main方法里,尝试调用a模块的方法,以及其子模块b中的方法
编译一下,会发现各种报错:
-----------------------------------------------------
(base) ➜ demo_1 git:(master) ✗ cargo build
Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1)
error[E0425]: cannot find function `foo_a_2` in this scope
--> src/bin/main.rs:12:13
|
12 | foo_a_2();
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
11 | use crate::a::foo_a_2;
|
error[E0425]: cannot find function `foo_a` in module `a`
--> src/bin/main.rs:19:8
|
19 | a::foo_a();
| ^^^^^ not found in `a`
error[E0603]: module `b` is private
--> src/bin/main.rs:20:8
|
20 | a::b::foo_b();
| ^ private module
|
note: the module `b` is defined here
--> src/bin/main.rs:10:5
|
10 | mod b {
| ^^^^^
Some errors have detailed explanations: E0425, E0603.
For more information about an error, try `rustc --explain E0425`.
error: could not compile `demo_1` due to 3 previous errors
-----------------------------------------------------
从提示上看,主要是private的问题:
- 默认情况下Rust里的函数以及模块,都是private作用域的,外界无法访问,所以要改成pub
修改一下:
mod a { pub fn foo_a_1() { println!("foo_a_1"); } //修改1:加pub pub fn foo_a_2(){ println!("foo_a_2"); } //修改2:加pub pub mod b { pub fn foo_b() { //修改3:调用父mod的方法,要加super关键字 super::foo_a_2(); println!("foo_b"); } } } fn main() { a::foo_a_1(); a::foo_a_2(); a::b::foo_b(); }
再运行:
(base) ➜ demo_1 git:(master) ✗ cargo run Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1) Finished dev [unoptimized + debuginfo] target(s) in 0.33s Running `target/debug/main` foo_a_1 foo_a_2 foo_a_2 foo_b
正常了,但是这里可能有同学会疑问:mod a不也没加pub关键字吗,为啥main能正常调用?可以先记一条规则 :如果模块x与main方法在一个.rs文件中,且x处于最外层,main方法可以调用x中的方法。
再微调下代码:
mod a { //修改:去掉pub fn foo_a_2(){ println!("foo_a_2"); } pub mod b { pub fn foo_b() { super::foo_a_2(); } } } fn main() { a::b::foo_b(); }
再次运行:
(base) ➜ demo_1 git:(master) ✗ cargo run Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1) Finished dev [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/main` foo_a_2
疑问:父模块mod a中的foo_a_2没加pub,也就是默认private,为啥子模块b能调用?又是一条规则 :子模块可以调用父模块中的private函数,但是反过来是不行的 (通俗点讲:老爸的钱,就是儿子的钱,但是儿子的钱,除非儿子主动给老爸,否则还是儿子的!想必Rust的设计者们,深知“父爱如山”的道理)。
mod a { pub fn foo_a_1(){ //这样是不行的,因为foo_b_2是private b::foo_b_2(); } //修改:去掉pub fn foo_a_2(){ println!("foo_a_2"); } pub mod b { pub fn foo_b() { super::foo_a_2(); } fn foo_b_2(){ println!("foo_b_2"); } } } fn main() { a::foo_a_1(); a::b::foo_b(); }
这样会报错。
3.2 简化访问路径
前面介绍过,main.rs就是cargo的入口,也可以理解为cargo的根,所以就本文的示例而言:
a::b::foo_b(); self::a::b::foo_b(); crate::a::b::foo_b();
是等效的,就好比,文件d:\a\b\1.txt,如果我们当前已经在d:\根目录下,
a\b\1.txt
d:\a\b\1.txt
.\a\b\1.txt
都能访问。
用全路径crate::a::b::foo_b()虽然能访问,但是代码看着太啰嗦了,可以用use来简化:
mod a { fn foo_a_2(){ println!("foo_a_2"); } pub mod b { pub fn foo_b() { super::foo_a_2(); } } } use crate::a::b::foo_b; fn main() { use crate::a::b::foo_b as x; foo_b(); x(); }
运行效果一样:
(base) ➜ demo_1 git:(master) ✗ cargo run Compiling demo_1 v0.1.0 (/Users/jimmy/code/demo_1) Finished dev [unoptimized + debuginfo] target(s) in 0.39s Running `target/debug/main` foo_a_2 foo_a_2
从上面的示例可以看到:
- use即可以在函数体内,也可以在函数外
- 当2个模块的函数有重名时,可以用use .. as .. 来取个别名
3.3 将mod拆分到多个文件
如上图,把mod a与b,分拆到a.rs, 及b.rs,与main.rs放在同1目录。注意main.rs的首二行:
mod a; mod b;
与常规mod不同的是,mod x后,并没有{...}代码块,而是;号,rust会在同级目录下,默认去找x.rs,再来看main方法:
fn main() { a::a::foo_a_2(); b::b::foo_b(); }
为何这里是a::a:: 连写2个a? 因为最开始声明mod a; 这里面已有1个模块a,而a.rs里首行,又定义了1个pub mod a,所以最终就是a::a::
如果mod太多,都放在一个目录下,也显得很乱,可以建个目录,把mod放到该到目录下:
(base) ➜ demo_1 git:(master) ✗ tree . ├── Cargo.lock ├── Cargo.toml └── src ├── abc │ ├── a.rs │ ├── b.rs │ └── mod.rs └── main.rs
这时要在该目录下,新增1个mod.rs,用于声明该目录下有哪些模块
pub mod a; pub mod b;
然后b.rs中引用a模块时,路径也要有所变化:
pub mod b { use crate::abc::a::a::foo_a_2; pub fn foo_b() { foo_a_2(); } }
main.cs里也要相应调整:
mod abc; fn main() { abc::a::a::foo_a_2(); abc::b::b::foo_b(); }
目录abc,本身就视为1个mod,所以main.rs里的mod abc; 就是声明abc目录为1个mod,然后再根据abc/mod.rs,进一步找到a, b二个mod