如何在Rust 2015中从一个模块导入/包含一个函数的基本方法?

58

我不知道如何将一个文件(模块)中的函数引入到另一个文件中(或者说导入、注入等其他词汇)。

我正在启动一个新项目:

$ cd ~/projects
$ cargo new proj --bin
$ cd proj
$ tree
.
|
-- Cargo.toml
-- src
   |
   -- main.rs

我需要修改 main.rs 文件,并创建一个新的文件 a.rs(在 src 目录下),其中包含以下代码:

main.rs

fn main() {
    println!("{}", a::foo());
}

a.rs

pub fn foo() -> i32 { 42 }

我使用cargo run运行项目时出现了错误:

error[E0433]: failed to resolve: use of undeclared type or module `a`
 --> src/main.rs:2:20
  |
2 |     println!("{}", a::foo());
  |                    ^ use of undeclared type or module `a`

看起来我需要以某种方式导入a。 我尝试将以下内容作为 main.rs 的第一行添加:

  • use a;

    error[E0432]: unresolved import `a`
     --> src/main.rs:1:5
      |
    1 | use a;
      |     ^ no `a` in the root
    
  • use a::*;

    error[E0432]: unresolved import `a`
     --> src/main.rs:1:5
      |
    1 | use a::*;
      |     ^ maybe a missing `extern crate a;`?
    
    error[E0433]: failed to resolve: use of undeclared type or module `a`
     --> src/main.rs:4:20
      |
    4 |     println!("{}", a::foo());
      |                    ^ use of undeclared type or module `a`
    
  • use a::foo;

    error[E0432]: unresolved import `a`
     --> src/main.rs:1:5
      |
    1 | use a::foo;
      |     ^ maybe a missing `extern crate a;`?
    
    error[E0433]: failed to resolve: use of undeclared type or module `a`
     --> src/main.rs:4:20
      |
    4 |     println!("{}", a::foo());
      |                    ^ use of undeclared type or module `a`
    
  • extern crate a; use a::foo;

    error[E0463]: can't find crate for `a`
     --> src/main.rs:1:1
      |
    1 | extern crate a;
      | ^^^^^^^^^^^^^^^ can't find crate
    
  • extern crate proj; use proj::a::foo;

    error[E0463]: can't find crate for `proj`
     --> src/main.rs:1:1
      |
    1 | extern crate proj;
      | ^^^^^^^^^^^^^^^^^^ can't find crate
    

我已经阅读了指南,但仍然无法弄清楚如何进行导入。

3个回答

57
在主模块(main.rs、lib.rs或subdir/mod.rs)中,您需要编写mod a;来使用在整个项目(或子目录)中想要使用的所有其他模块。
在任何其他模块中,您需要编写use a;use a::foo; 你并非唯一对此感到困惑的人,虽然可能有更好的解决方案,但任何有关于模块系统的更改都将被拒绝为"过于令人困惑"。
编辑:本答案是针对"Rust 2015"语言标准编写的。对于"Rust 2018"标准进行了更改,请参见此博客文章版本指南

1
那么什么时候需要使用 extern crate 呢?我以为每个 Rust 文件都是单独的 crate(编译单元)。 - voithos
3
您的 main.rs 或 lib.rs 文件以及通过 mod 指令递归引入的所有文件将被编译成一个 crate。这个单位就是编译的基本单元。 - Levans
36
对模块系统进行任何更改都会被拒绝,因为它们被认为是“过于复杂”。现有的模块系统已经“过于复杂”。 - Qix - MONICA WAS MISTREATED
9
我已经花了一个小时来尝试解决这个问题。没有其他语言的导入系统有如此多不同的关键字、含义、微妙差别和不明显的特性。 - Mateen Ulhaq
4
尽管我已经读了几遍有关模块的文档,但我实际上花费了40分钟才找到您在这里所陈述的内容。我有多年密集使用约3种语言和总共大约10种语言的经验。Rust的模块系统迄今为止是我见过的最不必要的过度复杂化... - Dmitry Kankalovich
显示剩余3条评论

25
在Rust中,有一些关键字用于处理模块:

mod

mod 有两种形式:

  • 当与花括号一起使用时,它声明一个模块(命名空间)。
  • 当只使用名称时,它将在本地文件系统中查找模块。

模块可以是:

  • 扩展名为 .rs 的文件或者
  • 包含名为 mod.rs 的文件夹

use

use 将名称导入当前模块的命名空间。我们可以使用 use 导入任何函数、结构体、枚举、类型别名、特性或(子)模块。use 语句非常严格,如果我们声明 use module1::moduleA;,那么来自 module1 的其他名称将不可用,仅有 moduleA 可用。可以使用星号 (*) 来使用模块内的所有内容:use module1::*;。也可以使用集合: use module1::{moduleA, moduleB};

例如:

| main.rs
|- module1
      |- mod.rs
      |- moduleA.rs
      |- moduleB.rs

mod.rs 包含:

pub mod moduleA; // declare a child module
pub mod moduleB; // declare a child module

main.rs 包含:

// use what Cargo downloaded (not necessary in Rust 2018)
//extern crate that_one_thing_i_need;

///  ======

mod module1; // declare a child module

// some local stuff I want to scope
mod local {
    pub fn my_function() {}
}

//   ======

// make the symbols locally available:
use module1::moduleA::*;
use module1::moduleB::{functionX, moduleY, typeZ};

// To avoid having to write `that_one_thing_i_need::` a lot,
// we can make local aliases that will be valid in the current module.
use that_one_thing_i_need::fancy_stuff as fs;

///  ======

fn main() {
    // we can use anything here from the namespaces we are using:
    //      moduleA
    //      functionX
    //      moduleY
    //      typeZ
    //      fs

    // We can access stuff by navigating from the outermost visible
    // module name
    local::my_function();
}

符号仅在模块内可用。如果要跨越这个限制(即使是在本地声明的模块中),我们需要使用关键字pub将它们公开。

extern crate

extern crate填补了Cargo和Rust之间的差距。我们在.rs文件中编写代码,该文件可以使用rustc编译。Cargo将管理外部依赖项并调用rustcextern crate ...行告诉编译器查找此命名空间,因此它是无歧义的。

编辑说明 — 在许多情况下,如果您使用Rust 2018版,则不需要extern crate


如何编写代码,让moduleA能够使用moduleB的内容?反之亦然? - Matthias
我曾经在过去多次遇到并仍然在苦恼于这种情况(一个模块从其“兄弟”或直接的“父级”导入某些内容)。简短的答案是使用 use crate::parentuse crate::sibling - Daniel Schütte
我建议在任何情况下都不要有相互依赖。更加谨慎的设计是将共同依赖项提取到另一个实体中,这将是一种更优雅的解决方案。 - Luis Ayuso

6
我很晚才加入这个团队,但有一种方式可以将您的代码分成多个文件而不会太大地影响作用域,具体如下。
假设您正在处理一个图书馆程序,并且采用以下文件夹结构:
src/
  lib.rs
  author.rs
  book.rs

你可以做以下事情:

// lib.rs
// --------------
mod author;
use author::*;
mod book;
use book::*;

// author.rs
// --------------
struct Author {
    name: String,
    birth_year: i32,
}

// book.rs
// --------------
use super::*;

struct Book {
   title: String,
   author: Author,  // Author is in scope
   isbn: i64,
}

这个结构模拟了Go模块(文件夹中的所有内容似乎都在同一作用域内)。
另一种方式是(更像Python风格):
// lib.rs
// --------------
mod author;
mod book;

// book.rs
// --------------
// either so, to import everything
use super::*;
// or so, one line per peer-module
use super::author;

struct Book {
   title: String,
   author: author::Author,  // Author is in scope
   isbn: i64,
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接