• Rust中的代码组织:package/crate/mod


    刚接触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 

  • 相关阅读:
    ajax代码及简单封装
    web开发中不同设备浏览器的区分
    JS实现带复选框的下拉菜单
    常用浏览器的编码设置
    PHP实现实现数字补零格式化
    Linux杂碎2/SHELL
    OS
    Linux sudoers
    代理缓存服务器squid
    es6
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/rust_package_crate_mod.html
Copyright © 2020-2023  润新知