Подтвердить что ты не робот

Как вы используете импорт родительского модуля в Rust?

Если у вас есть структура каталогов, подобная этой:

src/main.rs
src/module1/blah.rs
src/module1/blah2.rs
src/utils/logging.rs

Как вы используете функции из других файлов?

Из учебника по Rust кажется, что я должен быть в состоянии сделать это:

main.rs

mod utils { pub mod logging; }
mod module1 { pub mod blah; }

fn main() {
    utils::logging::trace("Logging works");
    module1::blah::doit();
}

logging.rs

pub fn trace(msg: &str) {
    println!(": {}\n", msg);
}

blah.rs

mod blah2;
pub fn doit() {
    blah2::doit();
}

blah2.rs

mod utils { pub mod logging; }
pub fn doit() {
    utils::logging::trace("Blah2 invoked");
}

Однако это приводит к ошибке:

error[E0583]: file not found for module 'logging'
 --> src/main.rs:1:21
  |
1 | mod utils { pub mod logging; }
  |                     ^^^^^^^
  |
  = help: name the file either logging.rs or logging/mod.rs inside the directory "src/utils"

Похоже, что импорт по пути, т.е. из main в module1/blah.rs, работает, а импорт пиров, т.е. blah2 из blah, работает, но импорт из родительской области не работает.

Если я использую магическую директиву #[path], я могу заставить это работать:

blah2.rs

#[path="../utils/logging.rs"]
mod logging;

pub fn doit() {
    logging::trace("Blah2 invoked");
}

Действительно ли мне нужно вручную использовать относительные пути к файлам для импорта чего-либо из уровня родительской области? Нет ли лучшего способа сделать это в Rust?

В Python вы используете from .blah import x для локальной области, но если вы хотите получить доступ к абсолютному пути, вы можете использовать from project.namespace.blah import x.

4b9b3361

Ответ 1

Я предполагаю, что вы хотите объявить utils и utils::logging на верхнем уровне, и просто хотите вызывать функции из них внутри module1::blah::blah2. Объявление модуля выполняется с помощью mod, который вставляет его в AST и определяет его канонический путь foo::bar::baz -style, а обычные взаимодействия с модулем (вне объявления) выполняются с помощью use.

// main.rs

mod utils {
    pub mod logging { // could be placed in utils/logging.rs
        pub fn trace(msg: &str) {
            println!(": {}\n", msg);
        }
    }
}

mod module1 {
    pub mod blah { // in module1/blah.rs
        mod blah2 { // in module1/blah2.rs
            // *** this line is the key, to bring utils into scope ***
            use crate::utils;

            pub fn doit() {
                utils::logging::trace("Blah2 invoked");
            }
        }

        pub fn doit() {
            blah2::doit();
        }
    }
}

fn main() {
    utils::logging::trace("Logging works");
    module1::blah::doit();
}

Единственным изменением, которое я сделал, была строка use crate::utils; в blah2 (в Rust 2015 вы также могли использовать use utils или use ::utils). Также смотрите вторую половину этого ответа для получения более подробной информации о том, как работает use. соответствующий раздел языка программирования Rust также является разумным справочником, в частности эти два подраздела:

Кроме того, обратите внимание, что я пишу все это в строке, помещая содержимое foo/bar.rs в mod foo { mod bar { <contents> } } напрямую, меняя его на mod foo { mod bar; } с соответствующим доступным файлом, должно быть идентично.

(Кстати, println(": {}\n", msg) печатает две новые строки; println! уже включает одну строку (ln - это "строка"), либо print!(": {}\n", msg) или println!(": {}", msg) печатают только одну.)


Нетрудно получить точную структуру, которую вы хотите, вы должны сделать одно изменение в расположении blah2.rs:

src
├── main.rs
├── module1
│   ├── blah
│   │   └── blah2.rs
│   └── blah.rs
└── utils
    └── logging.rs

main.rs

mod utils {
    pub mod logging;
}

mod module1 {
    pub mod blah;
}

fn main() {
    utils::logging::trace("Logging works");
    module1::blah::doit();
}

Utils/logging.rs

pub fn trace(msg: &str) { 
    println!(": {}\n", msg); 
}

module1/blah.rs

mod blah2;

pub fn doit() {
    blah2::doit();
}

module1/blah/blah2.rs (единственный файл, который требует каких-либо изменений)

// this is the only change

// Rust 2015
// use utils; 

// Rust 2018    
use crate::utils;

pub fn doit() {
    utils::logging::trace("Blah2 invoked");
}

Ответ 2

Я собираюсь ответить и на этот вопрос для всех, кто найдет это и (как и я) будет полностью сбит с толку трудными для понимания ответами.

Это сводится к двум вещам, которые, я чувствую, плохо объяснены в руководстве:

  • Синтаксис mod blah; импортирует файл для компилятора. Вы должны использовать это для всех файлов, которые хотите скомпилировать.

  • Как и совместно используемые библиотеки, любой определенный локальный модуль может быть импортирован в текущую область с помощью use blah::blah;.

Типичный пример:

src/main.rs
src/one/one.rs
src/two/two.rs

В этом случае вы можете получить код в one.rs из two.rs, используя use:

use two::two;  // <-- Imports two::two into the local scope as 'two::'

pub fn bar() {
    println!("one");
    two::foo();
}

Тем не менее, main.rs должен выглядеть примерно так:

use one::one::bar;        // <-- Use one::one::bar 
mod one { pub mod one; }  // <-- Awkwardly import one.rs as a file to compile.

// Notice how we have to awkwardly import two/two.rs even though we don't
// actually use it in this file; if we don't, then the compiler will never
// load it, and one/one.rs will be unable to resolve two::two.
mod two { pub mod two; }  

fn main() {
    bar();
}

Обратите внимание, что вы можете использовать файл blah/mod.rs для некоторого облегчения неловкости, поместив файл наподобие one/mod.rs, потому что mod x; пытается x.rs и x/mod.rs загружаться.

// one/mod.rs
pub mod one.rs

Вы можете уменьшить неудобный импорт файлов вверху main.rs до:

use one::one::bar;       
mod one; // <-- Loads one/mod.rs, which loads one/one.rs.
mod two; // <-- This is still awkward since we don't two, but unavoidable.    

fn main() {
    bar();
}

Есть пример проекта, который делает это на Github.

Стоит отметить, что модули не зависят от файлов, в которых содержатся блоки кода; хотя может показаться, что единственный способ загрузить файл blah.rs - это создать модуль с именем blah, вы можете использовать #[path], чтобы обойти это, если вам по какой-то причине это необходимо. К сожалению, кажется, что он не поддерживает подстановочные знаки, объединение функций из нескольких файлов в модуль верхнего уровня довольно утомительно.

Ответ 3

Если вы создаете файл с именем mod.rs, rustc будет смотреть на него при импорте модуля. Я бы предложил создать файл src/utils/mod.rs и сделать его содержимое примерно таким:

pub mod logging;

Затем в main.rs добавьте инструкцию следующим образом:

use utils::logging;

и назовите его

logging::trace(...);

или вы могли бы сделать

use utils::logging::trace;

...

trace(...);

В принципе, объявите свой модуль в файле mod.rs и use в исходных файлах.