모듈을 여러 파일로 분할


102

각각 자체 파일에 여러 구조체가있는 모듈을 갖고 싶습니다 . 사용하여 A Math예로서 모듈 :

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

각 구조체가 동일한 모듈에 있기를 원하며 다음과 같이 기본 파일에서 사용할 것입니다.

use Math::Vector;

fn main() {
  // ...
}

그러나 Rust의 모듈 시스템 (처음에는 약간 혼란 스럽습니다)은이를 수행하는 명백한 방법을 제공하지 않습니다. 전체 모듈을 하나의 파일로만 허용하는 것 같습니다. 소박하지 않습니까? 그렇지 않은 경우 어떻게해야합니까?


1
저는 "여러 구조체가있는 모듈을 각각 자체 파일에 포함하고 싶습니다."라고 해석했습니다. 즉, 자체 파일에서 각 구조체 정의를 원했습니다.
BurntSushi5

1
모듈 시스템이 확실히 그러한 구조를 허용하지만 이것은 소박한 것으로 간주되지 않습니다. 일반적으로 모듈 경로가 파일 시스템 경로에 직접 대응하는 것이 바람직합니다. 예를 들어 struct foo::bar::Bazfoo/bar.rs또는에 정의되어야합니다 foo/bar/mod.rs.
Chris Morgan

답변:


111

Rust의 모듈 시스템은 실제로 믿을 수 없을 정도로 유연하며 코드가 파일에서 어떻게 구조화되어 있는지 숨기면서 원하는 구조를 노출 할 수 있습니다.

여기서 핵심 pub use은 다른 모듈에서 식별자를 다시 내보낼 수 있는를 사용 하는 것입니다. Rust의 std::io상자에는 하위 모듈의 일부 유형 이 .NET에서 사용하기 위해 다시 내보내std::io 지는 선례가 있습니다 .

편집 (2019-08-25) : 답변의 다음 부분은 꽤 오래 전에 작성되었습니다. 이러한 모듈 구조를 rustc단독 으로 설정하는 방법을 설명합니다 . 오늘날 대부분의 사용 사례에 일반적으로 Cargo를 사용합니다. 다음은 여전히 ​​유효하지만 일부 (예 #![crate_type = ...]:)가 이상하게 보일 수 있습니다. 이것은 권장되는 솔루션이 아닙니다.

예제를 적용하기 위해 다음 디렉터리 구조로 시작할 수 있습니다.

src/
  lib.rs
  vector.rs
main.rs

여기 있습니다 main.rs:

extern crate math;

use math::vector;

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

그리고 당신 src/lib.rs:

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

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

그리고 마지막으로 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![] }
        }
    }
}

그리고 이것이 마법이 일어나는 곳입니다. 우리는 math::vector::vector_a특별한 종류의 벡터를 구현 한 하위 모듈 을 정의했습니다 . 그러나 라이브러리의 클라이언트가 vector_a하위 모듈 이 있다는 것을 신경 쓰지 않기를 바랍니다 . 대신 math::vector모듈 에서 사용할 수 있도록하고 싶습니다 . 이것은 현재 모듈에서 식별자 pub use self::vector_a::VectorA를 다시 내보내는으로 수행됩니다 vector_a::VectorA.

그러나 특수한 벡터 구현을 다른 파일에 넣을 수 있도록이를 수행하는 방법을 물었습니다. 이것이 mod vector_b;라인이하는 일입니다. Rust 컴파일러에게 vector_b.rs해당 모듈의 구현을위한 파일 을 찾도록 지시합니다 . 여기에 src/vector_b.rs파일이 있습니다.

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

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

클라이언트의 관점에서 사실 VectorAVectorB두 개의 서로 다른 파일에 두 개의 서로 다른 모듈에 정의는 완전히 불투명하다.

와 같은 디렉토리에있는 경우 다음을 사용 main.rs하여 실행할 수 있습니다.

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

일반적으로 Rust 책 의 "Crates and Modules"장은 꽤 좋습니다. 많은 예가 있습니다.

마지막으로, Rust 컴파일러는 자동으로 하위 디렉토리를 찾습니다. 예를 들어 위의 코드는 다음 디렉토리 구조에서 변경되지 않고 작동합니다.

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

컴파일 및 실행 명령도 동일하게 유지됩니다.


나는 당신이 "벡터"가 의미하는 바를 오해했다고 믿습니다. 나는 벡터에 대해 데이터 구조가 아니라 수학적 양 으로 말하고 있었다 . 또한, 저는 최신 버전의 녹을 실행하고 있지 않습니다. 왜냐하면 창문에 구축하는 것은 약간의 고통이기 때문입니다.
starscape 2014 년

+1 정확히 내가 필요로하는 것은 아니지만 올바른 방향을 가리 켰습니다.
starscape 2014 년

@EpicPineapple 맞아요! 그리고 Vec을 사용하여 이러한 벡터를 나타낼 수 있습니다. (물론 더 큰 N,하십시오.)
BurntSushi5

1
@EpicPineapple 업데이트 할 수 있도록 내 답변이 놓친 부분을 설명해 주시겠습니까? math::Vec2대신 사용 하는 것 외에 귀하의 답변과 내 답변의 차이점을 확인하기 위해 고군분투하고 있습니다 math::vector::Vec2. (즉, 동일한 개념이지만 하나 개의 모듈 깊이.)
BurntSushi5

1
귀하의 질문에는 해당 기준이 없습니다. 내가 볼 수있는 한 질문에 대답했습니다. (그는 실제로 파일에서 모듈을 분리하는 방법을 묻습니다.) Rust 0.9에서 작동하지 않는 것에 대해 미안하지만 불안정한 언어를 사용하는 영역이 있습니다.
BurntSushi5 2014 년

38

Rust 모듈 규칙은 다음과 같습니다.

  1. 소스 파일 자체 모듈 일뿐입니다 (특수 파일 main.rs, lib.rs 및 mod.rs 제외).
  2. 디렉토리 모듈 경로 구성 요소입니다.
  3. mod.rs 파일 은 디렉토리의 모듈 일뿐 입니다 .

math 디렉토리의 matrix.rs 1 파일 은 모듈 일뿐math::matrix 입니다. 그것은 간단합니다. 파일 시스템에서 보는 것은 소스 코드에서도 찾을 수 있습니다. 이것은 파일 경로와 모듈 경로 2 의 일대일 대응입니다 .

따라서 구조체가 math 디렉토리의 matrix.rs 파일 안에 있기 때문에을 사용 하여 구조체 Matrix를 가져올 수 있습니다 use math::matrix::Matrix. 행복하지 않습니까? 당신은 use math::Matrix;대신에 매우 선호 할 것입니다. 있을 수있다. 다음을 사용하여 math::matrix::Matrixmath / mod.rs에서 식별자 를 다시 내 보냅니다 .

pub use self::math::Matrix;

이 작업을 수행하는 또 다른 단계가 있습니다. Rust는 모듈을 로드 하기 위해 모듈 선언이 필요 합니다. mod math;main.rs에 추가하십시오. 그렇게하지 않으면 다음과 같이 가져올 때 컴파일러에서 오류 메시지가 표시됩니다.

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

여기서 힌트는 오해의 소지가 있습니다. 물론 별도의 라이브러리를 작성하려는 경우를 제외하고는 추가 상자가 필요하지 않습니다.

main.rs 상단에 다음을 추가하십시오.

mod math;
pub use math::Matrix;

모듈 선언은 하위 모듈 vector, matrix및에 대해서도 필요합니다. , 을 다시 내보내려면로드해야하기 complex때문 math입니다. 식별자의 다시 내보내기는 식별자 모듈을로드 한 경우에만 작동합니다. 이 수단은 재수출에 식별자는 math::matrix::Matrix당신이 작성해야합니다 mod matrix;. math / mod.rs에서이 작업을 수행 할 수 있습니다. 따라서 다음 내용으로 파일을 만듭니다.

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

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

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

그리고 당신은 끝났습니다.


1 소스 파일 이름은 일반적으로 Rust에서 소문자로 시작합니다. 이것이 내가 Matrix.rs가 아닌 matrix.rs를 사용하는 이유입니다.

2 Java는 다릅니다. 으로 경로를 선언합니다 package. 중복됩니다. 경로는 파일 시스템의 소스 파일 위치에서 이미 분명합니다. 파일 맨 위에있는 선언에서이 정보를 반복하는 이유는 무엇입니까? 물론 파일의 파일 시스템 위치를 찾는 대신 소스 코드를 빠르게 살펴 보는 것이 더 쉽습니다. 덜 헷갈 린다고 말하는 사람들을 이해할 수 있습니다.


23

Rusts 순수 주의자들은 아마도 저를 이단자라고 부르고이 해결책을 싫어할 것입니다. 그러나 이것은 훨씬 더 간단합니다. 각각의 파일을 각자의 파일에서 수행 한 다음 mod.rs에서 " include! "매크로를 사용하십시오.

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

이렇게하면 중첩 된 모듈이 추가되지 않고 복잡한 내보내기 및 다시 쓰기 규칙을 피할 수 있습니다. 간단하고 효과적이며 소란스럽지 않습니다.


1
네임 스페이스를 버렸습니다. 한 파일을 다른 파일과 관련이없는 방식으로 변경하면 이제 다른 파일이 손상 될 수 있습니다. '사용'의 사용이 누출됩니다 (즉, 모든 것이 다음과 같습니다 use super::*). 다른 파일에서 코드를 숨길 수 없습니다 (안전하지 않은 안전한 추상화를 위해 중요 함)
Demur Rumed

11
네,하지만 그것은 정확히 제가 원했던 것입니다. 네임 스페이스 목적을 위해 하나처럼 동작하는 여러 파일을 가지고 있습니다. 나는 모든 경우에 이것을 옹호하지는 않지만 어떤 이유로 든 "파일 당 하나의 모듈"방법을 다루고 싶지 않은 경우 유용한 해결 방법입니다.
hasvn

이것은 훌륭합니다. 내부 용이지만 자체적으로 포함 된 모듈의 일부가 있으며 이것이 트릭을 수행했습니다. 적절한 모듈 솔루션도 작동하도록 노력할 것입니다.하지만 그렇게 쉽지는 않습니다.
rjh

5
이단자라고해도 상관 없어요, 당신의 해결책은 편리 해요!
sailfish009

21

좋아, 잠시 동안 내 컴파일러와 싸웠고 마침내 작동하게되었습니다. (을 지적한 BurntSushi에게 감사드립니다 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;

수학 / 벡터 .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...
}

다른 구조체도 같은 방식으로 추가 할 수 있습니다. 참고 : 마스터가 아닌 0.9로 컴파일되었습니다.


4
참고의 사용 mod math;main.rs커플 당신의 main라이브러리와 프로그램. 당신이 원하는 경우 math모듈은 독립적으로 별도로 컴파일해야합니다와 함께 링크 extern crate math(내 대답 참조). Rust 0.9에서는 구문이 extern mod math대신 가능합니다 .
BurntSushi5

20
BurntSushi5의 답변을 올바른 답변으로 표시하는 것이 정말 공정했을 것입니다.
IluTov

2
@NSAddict 아니요. 파일에서 모듈을 분리하기 위해 별도의 상자를 만들 필요가 없습니다. 과도하게 설계되었습니다.
nalply

1
이 답변이 최다 투표가 아닌 이유는 무엇입니까 ?? 질문은 프로젝트를 몇 개의 파일로 나누는 방법을 물었습니다.이 답변이 보여주는 것처럼 간단하지만 상자로 나누는 방법이 아니라 더 어렵고 @ BurntSushi5가 대답 한 것입니다 (질문이 편집되었을 수 있습니까?). ..
Renato 2017 년

6
@ BurntSushi5의 답변은 수락 된 답변이어야합니다. 사회적으로 어색하고 질문을하고 매우 좋은 답변을 얻은 다음 별도의 답변으로 요약하고 요약을 수락 된 답변으로 표시하는 것을 의미 할 수도 있습니다.
hasanyasin

4

여기에 Rust 파일이 깊게 중첩 될 때 어떻게 포함하는지 추가하고 싶습니다. 다음과 같은 구조가 있습니다.

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

당신은 어떻게 액세스합니까 sink.rs또는 toilet.rs에서 main.rs?

다른 사람들이 언급했듯이 Rust는 파일에 대한 지식이 없습니다. 대신 모든 것을 모듈과 하위 모듈로 간주합니다. 욕실 디렉토리에있는 파일에 액세스하려면 파일을 내보내거나 맨 위로 정렬해야합니다. 액세스하려는 디렉토리와 pub mod filename_inside_the_dir_without_rs_ext파일 내부에 파일 이름을 지정 하면됩니다.

예.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. 디렉토리 bathroom.rs안에 라는 파일을 만듭니다 home.

  2. 파일 이름을 내 보냅니다.

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. home.rsnext 라는 파일을 만듭니다.main.rs

  4. pub mod bathroom.rs 파일

    // home.rs
    pub mod bathroom;
  5. 이내에 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 문을 사용할 수도 있습니다.

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

하위 모듈 내에 다른 형제 모듈 (파일) 포함

경우에 당신이 사용하려는 sink.rs에서 toilet.rs당신은 지정하여 모듈을 호출 할 수 있습니다, self또는 super키워드.

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

최종 디렉토리 구조

다음과 같은 결과를 얻게됩니다.

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

위의 구조는 Rust 2018 이상에서만 작동합니다. 다음 디렉터리 구조는 2018 년에도 유효하지만 2015 년에 사용했던 방식입니다.

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

home/mod.rs것과 동일 ./home.rs하고 home/bathroom/mod.rs동일하다 home/bathroom.rs. Rust는 디렉토리와 같은 이름의 파일을 포함하면 컴파일러가 혼란 스러울 것이기 때문에 이렇게 변경했습니다. 2018 버전 (먼저 표시된 버전)은 해당 구조를 수정합니다.

자세한 내용은 이 저장소 를 참조 하고 전체적인 설명은 이 YouTube 비디오 를 참조하십시오 .

마지막으로 ... 하이픈을 피하십시오! snake_case대신 사용하십시오 .

중요 사항

당신은 반드시 깊은 파일이 최상위들에 의해 필요하지 않은 경우에도 가기 배럴 모든 파일을.

즉, sink.rs을 발견 toilet.rs하려면 위의 방법을 사용하여 main.rs!

다시 말해서, 당신이 그들을 완전히 노출하지 않는 한 do pub mod sink;또는 use self::sink; inside toilet.rs작동 하지 않습니다main.rs !

따라서 항상 파일을 맨 위에 배치하는 것을 잊지 마십시오!


2
... 그것은 C ++에 비해 미친 듯이 복잡합니다. 이것은 무언가를 말하고 있습니다
Joseph Garvin

1
감사합니다.
etech
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.