王泰

V1

2022/07/28阅读:17主题:嫩青

Rust 入门指南(modules 和工程结构)

Rust 入门指南(modules 和工程结构)

前面

  • Rust 入门指南(rustup, cargo)
  • Rust 入门指南(crate 包管理)

第一篇文章中,我们讨论了「Rust 的安装」和「使用 cargo 工具创建新项目」。 在这篇文章中,我们将进一步了解 Rust 项目的结构,并深入了解 crates、modules 和 prelude 的概念。

如果你还没有 Rust 环境,

去安装 Rust 或者使用 https://geekcode.cloud 平台,

确保可以创建 rust 项目 :

$ cargo new hello_rust

创建一个新的可执行程序,因此您可以直接运行它:

$ cargo run

cargo 首先编译然后运行:

$ cargo run
“Hello, World!”

OK,接下来我们将讨论:

  • 默认的 Rust 项目结构
  • main.rs 文件
  • Rust Modules(文件)
  • Rust Modules和可见性
  • Rust Modules(文件夹)
  • 什么是 prelude

默认的 Rust 结构

Rust 默认基本结构如下,文件夹的结构是不能随意更改的:

hello_rust
  - src
    - main.rs
  - .gitignore
  - Cargo.toml

我们可以随时使用 cargo check 命令来检查文件夹结构和 Cargo.toml 文件。 如果出现了错误(比如我将 src 改名成 src1 ),cargo check 会提示:

error: failed to parse manifest at `/Users/geekcode/_working/scratch/hello_rust/Cargo.toml`Caused by:
 no targets specified in the manifest
either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present

例子中必须有一个src/main.rs 文件,因为我们创建的是 binary application。 如果我们创建的是 library(在cargo new 时添加参数--lib ),那么cargo 会为我们创建src/lib.rs

Cargo.lock 是自动生成的,不能修改。 Cargo 默认初始化一个 Git 存储库,还包含一个 .gitignore。

/target

执行 cargo build 自动创建target 文件夹,并在其中包含构建 artifacts 文件夹(根据不同的配置可能是 debugrelease 文件夹,默认为 debug)。

如果需要交叉编译到其他平台,那么会增加一个级别文件夹表示目标平台,然后才是构建配置(releasedebug)。

最后是 main.rs 文件,它是应用程序的入口,内容如下。

main.rs 文件

默认的 main.rs 文件非常简单:

fn main() {
  println!("Hello, world!");
}

main() 函数,这是我们应用程序的入口,它只打印“Helo, World!” 到标准输出。

请注意 println! 中的 println 函数是一个 Rust 宏(一种高级 Rust 语法功能),暂时可以忽略细节,现在只要记住它不是一个常规函数。

接下来我们要在main.rs 中愉快地写代码了,先来了解一下 modules 。

Modules

为了代码更加的简洁,我们添加一个 struct。 后面我们会把这段代码从主文件中移出,现在只需将 main.rs 改为:

struct MyStruct {}

fn main() {
 let _ms = MyStruct {};   <-- Note the '_'
}

这个 demo 很简单,它清晰的描述 Rust 的模块定义。

注意 _ 前缀变量名:如果定义了不使用的变量,Rust 会有警告。所以我们在变量前使用 _ 前缀,它可以通知编译器这是我们故意的,阻止编译器发出警告。 但在日常开发中我们不建议这么使用。

接下来我们要重构代码,将一个复杂的 struct 移到另一个文件中。 代码保持高内聚低耦合,我们建立了一新的文件 my_struct.rs

hello_rust
  - src
    - main.rs
    - my_struct.rs

文件必须被添加到 src/ 文件夹下,便编译器才能找到。 文件的命名规范使用 snake_case

把 struct 声明从 main.rs 中移动到 my_struct.rs 中:

// Contents of my_struct.rs
struct MyStruct {}

构建项目:

$ cargo build

因为 main.rs 删除了 struct 声明,所以显示如下错误:

Compiling hello_rust v0.1.0 (/scratch/hello_rust)
error[E0422]: cannot find struct, variant or union type `MyStruct` in this scope
 → src/main.rs:2:15
  |
2 | let _ms = MyStruct {};
  |^^^^^^^^ not found in this scope
error: aborting due to previous errorFor more information about this error, try `rustc — explain E0422`. error: could not compile `hello_rust`

Rust 告诉我们它无法找到 struct 的定义。 我们必须明确地声明 modules。 如果我们不声明,Rust 不会主动查找/编译 module。

引入 struct 声明,我们需要修改 main.rs 添加 module 引用,如下所示:

mod my_struct;
fn main() {
  let _ms = MyStruct {};
}

在 Rust 中所有文件和文件夹都是 module。 为了在模块中使用代码,我们需要首先使用 mod 语法 import 它。 本质上,这是在“mod my_struct;”语句的位置插入模块中的代码。

再次尝试构建。 等等,这是什么!? 它仍然不起作用……嗯。 我们来看看错误信息:

Compiling hello_rust v0.1.0 (/scratch/hello_rust)
error[E0422]: cannot find struct, variant or union type `MyStruct` in this scope
 → src/main.rs:4:15
  |
4 | let _ms = MyStruct {};
  |^^^^^^^^ not found in this scope
  |
help: consider importing this struct
  |
1 |use crate::my_struct::MyStruct;
  |

虽然错误是一样的,但现在有一个有用的提示关于添加:

use crate::my_struct::MyStruct;

我们将 main.rs 更改为如下所示:

mod my_struct;
use crate::my_struct::MyStruct;
fn main() {
  let _ms = MyStruct {};
}

当使用 mod 语句导入模块时,Rust 自动 为它创建一个模块命名空间(以避免冲突),因此我们无法直接访问我们的 struct 类型。 模块命名空间自动取自文件名(因为在这种情况下 module 是一个文件),因此 **my_struct**::MyStruct;use语句的一部分——它来自文件名 my_struct.rs(不带文件扩展名)。

use 语句中的crate:: 部分是因为所有 Rust 项目都是 crate。 Rust 项目可以由多个文件(modules)组成,文件也可以嵌套在文件夹中(也是 modules)。 使用crate::前缀表示来访问 module 树的根目录。

因此,再次查看我们的 main.rs :

mod my_struct;                  <-- Import the module code, placing
                                    it into the 'my_struct'
                                    namespaceuse 

use crate::my_struct::MyStruct; <-- Map the fully qualified (from
                                    the crate root) struct
                                    declaration
 to just 'MyStruct'

fn main() {
  let _ms = MyStruct {};        <-- Yay, we found it! .. or did we?
}

请记住这两点:

  • 必须使用 mod 引入 module(文件或文件夹)。
  • use 关键字可以方便地将完整限定的类型名称映射到类型名。

Modules - 可见性

我们现在运行上面的 main.rs 会出现新的错误:

Compiling hello_rust v0.1.0 (/scratch/hello_rust)
error[E0603]: struct `MyStruct` is private
 → src/main.rs:2:23
  |
2 | use crate::my_struct::MyStruct;
  |^^^^^^^^ private struct
  |

这告诉我们,虽然我们找到了 struct 声明,但模块的可见性是私有的,因此我们无法在此处访问它。

Rust 中的可见性与 Java 等语言略有不同,需要记住几条规则:

  • 模块内部的所有内容(即/src文件夹中的文件或子文件夹)可以访问该模块内的任何其他内容
  • 模块外部的所有内容只能访问该模块的公共成员。

可能看起来很奇怪,但它也有一些非常不错的作用——模块中的私有函数可以用于该模块的单元测试( Rust 将单元测试保留在模块中)。 其次,每个模块都必须声明一个公共接口,定义模块外部可以访问哪些成员。

要公开模块的成员,我们必须添加 pub 关键字。 让我们再次访问我们的 my_struct.rs 文件并将内容替换为:

pub struct MyStruct {}         <-- Add the 'pub' keyword

现在我们现在可以成功构建我们这个复杂的程序了 :)

注意:我们可以将 pub 放在大多数声明中,包括 struct、struct 字段、函数、常量等。

Modules - 文件夹

假如我们想将 MyStruct 拆分为多个文件。 当然,要将代码放到一个文件夹中。

Rust 可以以相同的方式(作为 modules)处理文件和文件夹,但有一个重要的区别。

我们创建一个名为 foo/ 的文件夹,我们的 MyStruct 是 foo 功能的一部分。 接下来将文件 my_struct.rs 移动到 /src/foo。 即,新的文件夹结构应该看起来像——

- src/
  - main.rs
  -foo/
    - my_struct.rs

现在修改 main.rs 包含我们的新模块 foo 替换 my_struct:

mod foo;                   <-- Change the module to match the folder
use crate::foo::MyStruct;  <-- Update the namespace to 'foo'

fn main() {
  let _ms = MyStruct {};
}

我们现在构建(cargo build),结果出错:

Compiling hello_rust v0.1.0 (/scratch/hello_rust)
error[E0583]:file not found for module `foo`
 → src/main.rs:1:1
  |
1 | mod foo;
  | ^^^^^^^^
  |
  = help:to create the module `foo`, create file “src/foo.rs” or “src/foo/mod.rs”

当引用文件夹 module 时,我们使用文件夹名称(就如同对文件 module 一样),但 Rust 需要文件夹中存在一个名为 mod.rs 的文件。

在这种情况下,我们可以简单地将 my_struct.rs 重命名为 mod.rs

为了完整点我们在 foo/ 文件夹中添加一个文件,其中包含另一个 struct 定义(想象中的名称为 Another):

// Contents of src/foo/another.rs
pub struct Another {}   <-- We're going to expose this as public
                            from the 'foo' module so that we can
                            use it in main.rs

我们将新模块导入 mod.rs 文件 -

// Contents of src/foo/mod.rs
pub mod another;        <-- Add the module import for 'another'
                            Note the use of 'pub' to expose the
                            module 'anotheras public from the
                            module 'foo'
pub struct MyStruct {}

最后在 main.rs 中使用我们新的 Another 结构

mod foo;
use crate::foo::MyStruct;
use crate::foo::another::Another; <-- Note that 'another' is a
                                      module within 'foo'
fn main() {
  let _ms = MyStruct {};
  let _a = Another {}; <-- Using prefix '_' as before
}

看起来有点啰嗦,所以我们接下来介绍 Preludes。

Preludes

修改 foo/ 中的 mod.rs

mod another;              <-- Remove the 'pub' modifier
pub use another::Another; <-- Add a use'ing to map Another directly
                              into 'foo' and make it public
pub struct MyStruct {}

我们不希望 another模块公开。我们先删除了 pub 关键字,然后用useAnother 的完全限定类型映射到 foo 命名空间(因为我们在 foo 模块中)。

最后,修改我们的 main.rs:

mod foo;
use crate::foo::{MyStruct,Another};
fn main() {
  let _ms = MyStruct {};
  let _a = Another {};
}

请注意,由于我们已经将 Another 的类型名称映射到了 foo 模块中,我们可以使用 use 一次导入多个名称。

这就是 PreludesPreludes 是一种模式,可以使你公开的所有想要的类型。

main.rs :

mod foo;

mod prelude {                            <-- Create module inline
  pub use crate::foo::{MyStruct,Another};<-- Note the 'pub' here!
}

use crate::prelude::*;                    <-- Make the types exposed
                                              in the prelude
                                              available
fn main() {
  let _ms = MyStruct {};
  let _a = Another {};
}

我们还可以将 Preludes 定义为另一个模块(使用 mod)

我们可以像使用其他任何模块一样使用“prelude”模块,例如在 mod.rs 文件中:

mod another;
pub use another::Another;
use crate::prelude::*;
pub struct MyStruct {}

虽然这个 case 其实不需要 preludes 。 但是你可以发现,只要是在 preludes 中声明了的 crate、标准库类型、常量和其他模块,你就可以用use 语句快速的访问到。

通过这个模块还可以发现:

  • 可以使用通配符 ::* 从模块中导入所有公共名称
  • 可以使用 crate:: 访问模块树的根目录(即本例中的主模块),并且可以在应用程序的任何位置执行此操作。

总结

Rust 中的模块系统绝对是该语言更令人困惑的点之一。 但是一旦了解了模块是什么(文件、文件夹)以及如何导入它们(mod),如何将名称映射到不同的模块(use),它就开始变得有简单了。

另外最重要的一点是,Rust 的一些文件名已经有了明确的含义(main.rs, lib.rs,mod.rs)不能修改。

Keep rusty!


加入 GeekCode Community 参与定义 Cloud Based IDE


点击【阅读原文】访问 GeekCode

分类:

后端

标签:

后端

作者介绍

王泰
V1