Divida um módulo em vários arquivos


102

Quero ter um módulo com várias estruturas, cada uma em seu próprio arquivo. Usando um Mathmódulo como exemplo:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Quero que cada estrutura esteja no mesmo módulo, que usaria do meu arquivo principal, assim:

use Math::Vector;

fn main() {
  // ...
}

No entanto, o sistema de módulos do Rust (que é um pouco confuso para começar) não fornece uma maneira óbvia de fazer isso. Parece que só permite que você tenha o módulo inteiro em um arquivo. Isso não é rústico? Se não, como faço isso?


1
Interpretei "Quero ter um módulo com várias estruturas, cada uma em seu próprio arquivo." para significar que você queria cada definição de estrutura em seu próprio arquivo.
BurntSushi5

1
Isso não seria considerado rústico, embora o sistema de módulos certamente permita tal estruturação. Geralmente é preferível que um caminho de módulo corresponda diretamente a um caminho de sistema de arquivos, por exemplo, struct foo::bar::Bazdeve ser definido em foo/bar.rsou foo/bar/mod.rs.
Chris Morgan

Respostas:


111

O sistema de módulos do Rust é incrivelmente flexível e permite que você exponha qualquer tipo de estrutura que desejar, enquanto oculta como seu código está estruturado em arquivos.

Acho que a chave aqui é fazer uso de pub use, o que permitirá que você reexporte identificadores de outros módulos. Há precedentes para isso na std::iocaixa de Rust, onde alguns tipos de submódulos são reexportados para uso emstd::io .

Editar (25/08/2019): a parte seguinte da resposta foi escrita há algum tempo. Ele explica como configurar essa estrutura de módulo rustcsozinho. Hoje, geralmente se usa Cargo para a maioria dos casos de uso. Embora o seguinte ainda seja válido, algumas partes (por exemplo #![crate_type = ...]) podem parecer estranhas. Esta não é a solução recomendada.

Para adaptar seu exemplo, podemos começar com esta estrutura de diretório:

src/
  lib.rs
  vector.rs
main.rs

Aqui está o seu main.rs:

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}

E seu src/lib.rs:

#[crate_id = "math"];
#[crate_type = "lib"];

pub mod vector; // exports the module defined in vector.rs

E finalmente src/vector.rs:

// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;

mod vector_b; // private sub-module defined in vector_b.rs

mod vector_a { // private sub-module defined in place
    #[derive(Debug)]
    pub struct VectorA {
        xs: Vec<i64>,
    }

    impl VectorA {
        pub fn new() -> VectorA {
            VectorA { xs: vec![] }
        }
    }
}

E é aqui que a mágica acontece. Definimos um submódulo math::vector::vector_aque tem alguma implementação de um tipo especial de vetor. Mas não queremos que os clientes de sua biblioteca se importem que haja um vector_asubmódulo. Em vez disso, gostaríamos de disponibilizá-lo no math::vectormódulo. Isso é feito com o pub use self::vector_a::VectorA, que reexporta o vector_a::VectorAidentificador no módulo atual.

Mas você perguntou como fazer isso para que pudesse colocar suas implementações vetoriais especiais em arquivos diferentes. É isso que a mod vector_b;linha faz. Ele instrui o compilador Rust a procurar um vector_b.rsarquivo para a implementação desse módulo. E com certeza, aqui está nosso src/vector_b.rsarquivo:

#[derive(Debug)]
pub struct VectorB {
    xs: Vec<i64>,
}

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}

Do ponto de vista do cliente, o fato de VectorAe VectorBserem definidos em dois módulos diferentes em dois arquivos diferentes é totalmente opaco.

Se você estiver no mesmo diretório que main.rs, deverá ser capaz de executá-lo com:

rustc src/lib.rs
rustc -L . main.rs
./main

Em geral, o capítulo "Caixas e Módulos" no livro Rust é muito bom. Existem muitos exemplos.

Finalmente, o compilador Rust também procura em subdiretórios para você automaticamente. Por exemplo, o código acima funcionará inalterado com esta estrutura de diretório:

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs

Os comandos para compilar e executar também permanecem os mesmos.


Eu acredito que você entendeu mal o que eu quis dizer com "vetor". Eu estava falando de vetor como uma quantidade matemática , não a estrutura de dados. Além disso, não estou executando a versão mais recente do Rust, porque é um pouco complicado construir no Windows.
starscape

+1 Não era exatamente o que eu precisava, mas me apontou na direção certa.
starscape

@EpicPineapple De fato! E um Vec pode ser usado para representar esses vetores. (Para N maior, é claro.)
BurntSushi5

1
@EpicPineapple Você poderia explicar o que minha resposta falhou para que eu possa atualizá-la? Estou lutando para ver a diferença entre a sua resposta e a minha diferente de usar em math::Vec2vez de math::vector::Vec2. (ou seja, mesmo conceito, mas um módulo mais profundo.)
BurntSushi5

1
Não vejo esse critério em sua pergunta. Pelo que posso ver, respondi à pergunta feita. (O que realmente estava perguntando como divorciar módulos de arquivos.) Desculpe por não funcionar no Rust 0.9, mas isso vem com o território de usar uma linguagem instável.
BurntSushi5

38

As regras do módulo Rust são:

  1. Um arquivo fonte é apenas seu próprio módulo (exceto os arquivos especiais main.rs, lib.rs e mod.rs).
  2. Um diretório é apenas um componente do caminho do módulo.
  3. O arquivo mod.rs é apenas o módulo do diretório.

O arquivo matrix.rs 1 no diretório math é apenas o módulo math::matrix. É fácil. O que você vê em seu sistema de arquivos, também encontra em seu código-fonte. Esta é uma correspondência de um para um de caminhos de arquivo e caminhos de módulo 2 .

Então você pode importar uma estrutura Matrixcom use math::matrix::Matrix, porque a estrutura está dentro do arquivo matrix.rs em um diretório matemático. Infeliz? Você prefere use math::Matrix;muito, não é? É possível. math::matrix::MatrixExporte novamente o identificador em math / mod.rs com:

pub use self::math::Matrix;

Há outra etapa para fazer isso funcionar. Rust precisa de uma declaração de módulo para carregar o módulo. Adicione um mod math;em main.rs. Se você não fizer isso, receberá uma mensagem de erro do compilador ao importar assim:

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

A dica é enganosa aqui. Não há necessidade de caixas adicionais, exceto, é claro, que você realmente pretende escrever uma biblioteca separada.

Adicione isso no topo de main.rs:

mod math;
pub use math::Matrix;

A declaração do módulo também é necessária para os submódulos vector, matrixe complex, porque mathprecisa carregá-los para reexportá-los. A reexportação de um identificador só funciona se você carregou o módulo do identificador. Isso significa que, para reexportar o identificador, math::matrix::Matrixvocê precisa escrever mod matrix;. Você pode fazer isso em math / mod.rs. Portanto, crie o arquivo com este conteúdo:

mod vector;
pub use self::vector::Vector;

mod matrix;
pub use self::matrix::Matrix;

mod complex;
pub use self::complex::Complex;

Aaaand você está feito.


1 Os nomes dos arquivos de origem geralmente começam com uma letra minúscula em Rust. É por isso que uso matrix.rs e não Matrix.rs.

2 Java é diferente. Você declara o caminho com packagetambém. É redundante. O caminho já é evidente a partir da localização do arquivo de origem no sistema de arquivos. Por que repetir essas informações em uma declaração no início do arquivo? Claro que às vezes é mais fácil dar uma olhada rápida no código-fonte em vez de descobrir a localização do arquivo no sistema de arquivos. Posso entender as pessoas que dizem que é menos confuso.


23

Os puristas de Rusts provavelmente me chamarão de herege e odiarão essa solução, mas isso é muito mais simples: basta fazer cada coisa em seu próprio arquivo e, em seguida, usar a macro " incluir! " No mod.rs:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

Dessa forma, você não obtém módulos aninhados adicionados e evita regras complicadas de exportação e reescrita. Simples, eficaz, sem complicações.


1
Você apenas descartou o namespacing. Alterar um arquivo de uma forma não relacionada a outro agora pode corromper outros arquivos. O uso de 'uso' torna-se permeável (ou seja, tudo é igual use super::*). Você não pode ocultar o código de outros arquivos (o que é importante para abstrações seguras de uso inseguro)
Demur Rumed

11
Sim, mas isso é exatamente o que eu queria nesse caso: ter vários arquivos que se comportassem como apenas um para fins de namespacing. Não estou defendendo isso para todos os casos, mas é uma solução útil se você não quiser lidar com o método "um módulo por arquivo", por qualquer motivo.
hasvn

Isso é ótimo, tenho uma parte do meu módulo que é apenas interno, mas independente, e funcionou. Vou tentar fazer com que a solução de módulo adequada funcione também, mas não é nada fácil.
rjh

5
não me importo em ser chamado de herege, sua solução é conveniente!
sailfish009,

21

Tudo bem, lutei com meu compilador por um tempo e finalmente consegui fazê-lo funcionar (obrigado a BurntSushi por apontar pub use.

main.rs:

use math::Vec2;
mod math;

fn main() {
  let a = Vec2{x: 10.0, y: 10.0};
  let b = Vec2{x: 20.0, y: 20.0};
}

math / mod.rs:

pub use self::vector::Vec2;
mod vector;

math / vector.rs

use std::num::sqrt;

pub struct Vec2 {
  x: f64,
  y: f64
}

impl Vec2 {
  pub fn len(&self) -> f64 {
    sqrt(self.x * self.x + self.y * self.y) 
  }

  // other methods...
}

Outras estruturas podem ser adicionadas da mesma maneira. NOTA: compilado com 0.9, não mestre.


4
Note-se que seu uso mod math;em main.rscasais seu mainprograma com a sua biblioteca. Se você quiser que seu mathmódulo seja independente, você precisará compilá-lo separadamente e vinculá-lo a extern crate math(conforme mostrado em minha resposta). No Rust 0.9, é possível que a sintaxe seja extern mod math.
BurntSushi5

20
Realmente teria sido justo marcar a resposta de BurntSushi5 como a correta.
IluTov

2
@NSAddict Não. Para separar os módulos dos arquivos, você não precisa criar uma caixa separada. É super-projetado.
basicamente

1
Por que esta não é a resposta mais votada ?? A pergunta feita sobre como dividir o projeto em alguns arquivos, que é tão simples quanto esta resposta mostra, não como dividi-lo em caixas, o que é mais difícil e foi o que @ BurntSushi5 respondeu (talvez a pergunta tenha sido editada?). ..
Renato

6
A resposta de @ BurntSushi5 deveria ter sido aceita. É socialmente desajeitado e talvez até signifique fazer uma pergunta, obter uma resposta muito boa e, em seguida, resumir como uma resposta separada e marcar seu resumo como a resposta aceita.
hasanyasin

4

Eu gostaria de adicionar aqui como você inclui arquivos Rust quando eles estão profundamente aninhados. Tenho a seguinte estrutura:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

Como você acessa sink.rsou toilet.rsde main.rs?

Como outros já mencionaram, Rust não tem conhecimento de arquivos. Em vez disso, ele vê tudo como módulos e submódulos. Para acessar os arquivos dentro do diretório do banheiro, você precisa exportá-los ou colocá-los no topo. Você faz isso especificando um nome de arquivo com o diretório que deseja acessar e pub mod filename_inside_the_dir_without_rs_extdentro do arquivo.

Exemplo.

// sink.rs
pub fn run() { 
    println!("Wash my hands for 20 secs!");
}

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Crie um arquivo chamado bathroom.rsdentro dohome diretório:

  2. Exporte os nomes dos arquivos:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Crie um arquivo chamado home.rspróximo amain.rs

  4. pub mod o arquivo bathroom.rs

    // home.rs
    pub mod bathroom;
  5. Dentro main.rs

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    mod home;
    
    fn main() {
        home::bathroom::sink::run();
    }

    use declarações também podem ser usadas:

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    use home::bathroom::{sink, toilet};
    
    fn main() {
        sink::run();
        sink::toilet();
    }

Incluindo outros módulos irmãos (arquivos) dentro de submódulos

No caso de desejar usar sink.rsde toilet.rs, você pode chamar o módulo especificando as palavras-chave selfou super.

// inside toilet.rs
use self::sink;
pub fn run() {
  sink::run();
  println!("Ahhh... This is sooo relaxing.")
}

Estrutura Final do Diretório

Você acabaria com algo assim:

|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

A estrutura acima só funciona com o Rust 2018 em diante. A seguinte estrutura de diretório também é válida para 2018, mas é como 2015 costumava funcionar.

|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs

Em que home/mod.rsé o mesmo que ./home.rse home/bathroom/mod.rsé o mesmo que home/bathroom.rs. Rust fez essa alteração porque o compilador ficaria confuso se você incluísse um arquivo com o mesmo nome do diretório. A versão 2018 (a mostrada primeiro) corrige essa estrutura.

Veja este repositório para obter mais informações e este vídeo do YouTube para uma explicação geral.

Uma última coisa ... evite hífens! Use em seu snake_caselugar.

Nota importante

Você deve colocar todos os arquivos no topo, mesmo se arquivos profundos não forem exigidos pelos de nível superior.

Isso significa que, para sink.rsdescobrir toilet.rs, você precisa barrá-los usando os métodos acima atémain.rs !

Em outras palavras, fazer pub mod sink;ou use self::sink; dentro toilet.rsvai não trabalho a menos que você tê-los expostos por todo o caminho até main.rs!

Portanto, lembre-se sempre de colocar seus arquivos no topo!


2
... que é insanamente complicado em comparação com C ++, que está dizendo algo
Joseph Garvin

1
Melhor resposta, obrigado.
Etech
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.