web-dev-qa-db-fra.com

Comment utilisez-vous les importations de module parent dans Rust?

Si vous avez une structure de répertoires comme celle-ci:

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

Comment utilisez-vous les fonctions d'autres fichiers?

D'après le tutoriel Rust, il semble que je devrais pouvoir faire ceci:

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");
}

Cependant, cela produit une erreur:

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"

Il semble que l'importation sur le chemin, c'est-à-dire de main à module1/blah.rs fonctionne et importe des pairs, c'est-à-dire blah2 from blah fonctionne, mais l'importation à partir de la portée parent ne fonctionne pas.

Si j'utilise la magie #[path] directive, je peux faire ça:

blah2.rs

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

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

Dois-je vraiment utiliser manuellement des chemins de fichier relatifs pour importer quelque chose à partir d'un niveau de portée parent? N'y a-t-il pas une meilleure façon de faire cela à Rust?

En Python, vous utilisez from .blah import x pour l'étendue locale, mais si vous souhaitez accéder à un chemin absolu, vous pouvez utiliser from project.namespace.blah import x.

34
Doug

Je suppose que vous voulez déclarer utils et utils::logging Au niveau supérieur, et que vous souhaitez simplement appeler des fonctions à partir d'eux dans module1::blah::blah2. La déclaration d'un module se fait avec mod, qui l'insère dans le AST et définit son chemin de style canonique foo::bar::baz, Et les interactions normales avec un module (loin de la déclaration) se fait avec 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 utils;

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

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

La seule modification que j'ai apportée est la ligne use utils Dans blah2. Voir aussi la deuxième moitié de cette réponse pour plus de détails sur le fonctionnement de use. Le section pertinente de Le Rust langage de programmation est également une référence raisonnable, en particulier ces deux sous-sections:

Notez également que j'écris tout cela en ligne, en plaçant directement le contenu de foo/bar.rs Dans mod foo { mod bar { <contents> } }, En le changeant en mod foo { mod bar; } Avec le fichier approprié disponible qui doit être identique.

(Soit dit en passant, println(": {}\n", msg) imprime deux nouvelles lignes; println! En inclut déjà une (le ln est "ligne"), soit print!(": {}\n", msg) ou println!(": {}", msg) n'en imprimer qu'un.)


Pour obtenir la structure exacte que vous souhaitez:

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/blah2.rs (le seul fichier qui nécessite des modifications)

use utils; // this is the only change

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

Je vais également répondre à cette question, pour quiconque le trouve et est (comme moi) complètement dérouté par les réponses difficiles à comprendre.

Cela se résume à deux choses qui, selon moi, sont mal expliquées dans le didacticiel:

  • Le mod blah; la syntaxe importe un fichier pour le compilateur. Vous devez l'utiliser sur tous les fichiers que vous souhaitez compiler .

  • En plus des bibliothèques partagées, tout module local défini peut être importé dans la portée actuelle à l'aide de use blah::blah;.

Un exemple typique serait:

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

Dans ce cas, vous pouvez avoir du code dans one.rs de two.rs en utilisant use:

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

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

Pourtant, main.rs devra être quelque chose comme:

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();
}

Notez que vous pouvez utiliser le blah/mod.rs fichier pour atténuer quelque peu la gêne, en plaçant un fichier comme one/mod.rs, car mod x; tentatives x.rs et x/mod.rs en tant que charges.

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

Vous pouvez réduire les importations de fichiers maladroites en haut de 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();
}

Il y a un exemple de projet le faisant sur Github .

Il convient de noter que les modules sont indépendants des fichiers dans lesquels les blocs de code sont contenus; bien que cela semble être le seul moyen de charger un fichier blah.rs consiste à créer un module appelé blah, vous pouvez utiliser le #[path] pour contourner cela, si vous en avez besoin pour une raison quelconque. Malheureusement, il ne semble pas prendre en charge les caractères génériques, l'agrégation de fonctions de plusieurs fichiers dans un module de niveau supérieur est plutôt fastidieuse.

27
Doug

Si vous créez un fichier appelé mod.rs, rustc l'examinera lors de l'importation d'un module. Je vous suggère de créer le fichier src/utils/mod.rs, et faire ressembler son contenu à ceci:

pub mod logging;

Puis dans main.rs, ajoutez une déclaration comme celle-ci:

use utils::logging;

et appelez-le avec

logging::trace(...);

ou tu pourrais faire

use utils::logging::trace;

...

trace(...);

Fondamentalement, déclarez votre module dans le mod.rs fichier et use dans vos fichiers source.

0
a_m0d