当存在main.rs和lib.rs时,Rust模块混乱

Rust modules confusion when there is main.rs and lib.rs

我有4个文件:

main.rs

1
2
3
4
5
6
mod bar;

fn main() {
    let v = vec![1, 2, 3];
    println!("Hello, world!");
}

lib.rs

1
2
pub mod foo;
pub mod bar;

foo.rs

1
2
3
pub fn say_foo() {

}

bar.rs

1
2
3
4
5
use crate::foo;

fn bar() {
    foo::say_foo();
}

运行cargo run时出现错误消息:

1
2
3
4
5
error[E0432]: unresolved import `crate::foo`
 --> src/bar.rs:1:5
  |
1 | use crate::foo;
  |     ^^^^^^^^^^ no `foo` in the root

有人可以向我解释如何解决此问题吗?更广泛地说:当存在main.rslib.rs时,模块查找如何工作?

编辑:将mod foo添加到main.rs可解决此问题。但是我不明白这一点-我给人的印象是lib.rs是"暴露"所有模块的地方?为什么还要在main.rs中声明模块?

我的Cargo.toml

1
2
3
4
5
6
7
8
9
[package]
name ="hello-world"
version ="0.1.0"
authors = ["[email protected]>"]
edition ="2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]


让我们从头开始。查看《货运手册》中的"包装布局"一章。如您所见,您的包中可能包含很多东西:

  • 一个二进制文件(可以运行的文件)或多个二进制文件,
  • 单个库(共享代码),
  • 例子),
  • 基准,
  • 集成测试。

包装布局

此处未列出所有可能性,仅列出了二进制文件/库组合。

二进制文件

这是带有单个二进制文件的软件包的示例。入口点是src/main.rs中的main函数。

Cargo.toml

1
2
3
4
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

src/main.rs

1
2
3
fn main() {
    println!("Hallo, Rust here!")
}
1
2
$ cargo run
Hallo, Rust here!

图书馆

这是带有库的软件包的示例。图书馆没有入口点,您不能运行它们。它们用于功能共享。

Cargo.toml

1
2
3
4
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

src/lib.rs

1
2
3
pub fn foo() {
    println!("Hallo, Rust library here!")
}
1
2
$ cargo run
error: a bin target must be available for `cargo run`

您是否在Cargo.toml文件中看到有关二进制文件或库的任何内容?否。原因是我遵循了程序包布局,并且cargo知道在哪里寻找东西。

二进制文件和库

这是带有二进制文件和库的软件包的示例。

Cargo.toml

1
2
3
4
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

src/lib.rs

1
pub const GREETING: &'static str ="Hallo, Rust library here!";

src/main.rs

1
2
3
4
5
use hallo::GREETING;

fn main() {
    println!("{}", GREETING);
}

相同的问题,您是否在Cargo.toml文件中看到有关二进制文件或库的任何内容?编号

此程序包包含两件事:

  • 二进制文件(根src/main.rs,入口点src/main.rs::main),
  • 库(根src/lib.rs,共享代码)。

可通过use hallo::...从二进制文件中引用库,其中hallo是此程序包名称(Cargo.toml-> [package]-> name)。

你的问题

Cargo.toml

1
2
3
4
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

相同的包装布局

库部分

src/lib.rs

1
2
pub mod bar;
pub mod foo;

src/foo.rs

1
2
3
pub fn say_foo() {
    println!("Foo");
}

src/bar.rs

1
2
3
4
5
use crate::foo;

pub fn bar() {
    foo::say_foo();
}

crate指的是src/lib.rs,因为我们在这里是我们库的上下文。

将其作为独立的单元进行处理,并通过use hallo::...;进行引用。

二进制部分

src/main.rs

1
2
3
4
5
use hallo::bar::bar;

fn main() {
    bar();
}

这里我们只是在使用我们的库。

没有图书馆

相同的代码,但是lib.rs被重命名为utils.rs,并且(foo|bar).rs文件被移动到src/utils/文件夹中。

src/utils.rs

1
2
pub mod bar;
pub mod foo;

src/utils/foo.rs

1
2
3
pub fn say_foo() {
    println!("Foo");
}

src/utils/bar.rs

1
2
3
4
5
6
use super::foo;
// or use crate::utils::foo;

pub fn bar() {
    foo::say_foo();
}

我们也可以在这里使用crate,但是由于我们处于二进制环境中,因此路径有所不同。

src/main.rs

1
2
3
4
5
6
7
use utils::bar::bar;

mod utils;

fn main() {
    bar();
}

在这里,我们刚刚声明了另一个模块(utils),我们正在使用它。

概要

Cargo.toml内容:

1
2
3
4
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

如果有src/main.rs文件,则基本上是这样说的:

1
2
3
4
5
6
7
8
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

[[bin]]
name ="hallo"
src ="src/main.rs"

如果有src/lib.rs文件,则基本上是这样说的:

1
2
3
4
5
6
7
8
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

[lib]
name ="hallo"
path ="src/lib.rs"

如果两者都存在,基本上就是这样说:

1
2
3
4
5
6
7
8
9
10
11
12
[package]
name ="hallo"
version ="0.1.0"
edition ="2018"

[[bin]]
name ="hallo"
path ="src/main.rs"

[lib]
name ="hallo"
path ="src/lib.rs"

文献资料

  • 包装布局
  • 清单格式
  • 使用软件包,板条箱和模块管理不断增长的项目


简而言之,Rust官方书上写着:

If a package contains src/main.rs and src/lib.rs, it has two crates: a library and a binary, both with the same name as the package.

此外,Rust参考说明:

crate resolves the path relative to the current crate

因此,您的项目中实际上有两个包装箱,crate限定词解析为哪个包装箱取决于您调用它的位置。

现在在代码示例中,如果要编译内容,则必须从src/main.rs中删除mod bar;。否则,您将声明bar是两个包装箱中的模块。

将其删除后,则因为在src/lib.rs中具有:

1
2
pub mod foo;
pub mod bar;

bar现在将成为src/lib.rs板条箱中的模块,因此bar.rs中的crate限定词将引用src/lib.rshello-world板条箱,这就是您想要的。铅>

还有一件事,如果您想从src/main.rs访问src/lib.rs中公开的项目,则必须按照@zrzka所说的做,即命名src/lib.rssrc/main.rs分享。例如,在名为hello-world

的项目中

1
2
3
4
use hello_world::foo;
fn main() {
    foo::say_foo();
}

是将src/lib.rs中声明的foo模块导入到src/main.rs中的方式。

但是,导入行为似乎没有其他效果。即如果在src/main.rs中声明了一些公共模块,则即使指定了包装箱的名称,也无法将其导入到src/lib.rs包装箱中。我找不到描述此行为的文档,但是通过在Rust 1.37.0中对其进行测试,情况似乎确实如此。


lib.rsmain.rs文件是软件包的两个独立入口点。

当您使用cargo run(或构建二进制文件并明确运行它)时,要使用的入口点是main.rs,而crate关键字指的是二进制条板箱。它甚至不必知道lib.rs中是否包含某些内容:二进制文件将像对待任何其他外部条板箱一样对待该库,并且必须通过extern crate hello_world或例如use hello_world::foo将其导入。

但是,导入库时,入口点是lib.rs,而crate是库箱。在这种情况下,是的,您添加到lib.rs的所有内容都暴露在整个包装箱中。

在这种情况下,通常的工作流程是使二进制文件像库周围的薄包装纸一样-在某些极端情况下,main.rs仅包含类似

的东西

1
2
3
4
use library;
fn main() {
    library::main();
}

和整个逻辑(以及所有项目结构)进入库箱。原因之一就是您遇到的问题:是否将此具体模块导入包装中的每个板条箱中可能引起混淆。