Rust의 모듈 시스템

2020년 7월 20일 수정

Rust의 모듈 시스템은 좀 독특한 편이다. 몇 가지 시험을 해보면서 알아낸 것들을 정리해보자.

모듈 시스템의 기초

일단 모듈은 mod 라는 키워드를 이용해 코드 어디서든 정의할 수 있다.

mod foo {
    pub fn public_foo() {
        println!("foo::public_foo!")
    }

    fn private_foo() {
        println!("foo::private_foo!")
    }
}

fn main() {
    foo::public_foo();
}

위 예제는 main 함수가 있는 main.rs 파일에 모듈을 정의한 예제다.

이렇게 정의된 모듈은 별도의 이름으로 네임스페이스가 분리되며 :: 을 구분자로 모듈 내부의 함수에 접근할 수 있다.

다만 함수의 경우 pub 로 표기되지 않은 함수는 외부에서는 볼 수 없기 때문에 호출할 수 없다. mod 로 정의되는 모듈의 경우도 마찬가지인데, 위처럼 자신의 파일에 정의된 모듈에 한해서는 pub 선언이 없어도 접근 가능한 것은 알 수 있다.

모듈 계층 구조

디렉터리 구조 처럼 Rust의 모듈 시스템도 계층(hierarchy) 구조를 지원한다. 즉 모듈 안에 모듈을 선언할 수 있다.

mod foo {
    pub fn public_foo() {
        println!("foo::public_foo!")
    }

    fn private_foo() {
        println!("foo::private_foo!")
    }

    mod private_bar {
        pub fn public_bar() {
            println!("foo::private_bar::public_bar!")
        }
    }

    pub mod bar {
        pub fn public_bar() {
            println!("foo::bar::public_bar!")
        }
    }
}

fn main() {
    foo::public_foo();
    foo::bar::public_bar();
}

mod 안에도 mod 를 선언할 수 있다. 따라서 깊이에 상관 없이 복잡한 모듈 구조를 디자인 할 수 있다.

이번에도 같은 파일에 있는 모듈이지만, 모듈 안의 모듈은 기본적으로 private가 되기 때문에 pub 가 붙어있지 않은 foo::private_bar 모듈에는 접근할 수 없었다.

외부 파일로 모듈 분리하기

사실 한 파일 안에 모듈을 같이 선언하는 것은 정말 무의미한 행위다. 모듈은 애초에 코드를 용도에 맞게 분리하고 정리하려는 목적으로 사용하니 말이다.

이제 위에서 선언한 모듈을 별도의 파일로 분리해보자. 아래와 같이 foo 모듈을 foo.rs 라는 별도의 파일로 떼어냈다.

pub fn public_foo() {
    println!("foo::public_foo!")
}

fn private_foo() {
    println!("foo::private_foo!")
}

mod private_bar {
    pub fn public_bar() {
        println!("foo::private_bar::public_bar!")
    }
}

pub mod bar {
    pub fn public_bar() {
        println!("foo::bar::public_bar!")
    }
}

mod 가 사라졌는지 의아할 수도 있는데 main.rs 파일을 보면 알 수 있을지도 모르겠다. main.rs 파일은 아래처럼 수정했다.

mod foo;

fn main() {
    foo::public_foo();
    foo::bar::public_bar();
}

기존의 foo 모듈 구현이 생략되고 바로 세미콜론이 찍혀있다. 즉 modmain.rs 에는 그대로 남아있다.

이런 식으로 이제 foo.rs 파일로 분리된 foo 모듈을 사용할 수 있다.

마치 mod 의 역할이 C 언어#include 와 비슷한 목적인가라는 생각이 들기도 했다.

디렉터리로 모듈 분리하기

이렇게 파일 단위로 모듈을 분리하는 것도 나쁜 선택은 아니지만, 좀 더 깔끔한 디렉터리 구조를 디자인하기 위해 아예 모듈을 별도의 디렉터리로 분리할 수도 있다. 이번엔 새로운 모듈 quu를 하위 디렉터리에 만드는 예를 보자.

우선 quu 라는 디렉터리를 만들고 quu/mod.rs 파일을 아래와 같은 내용을 만들었다.

pub fn public_quu() {
    println!("public_quu!")
}

이제 새로 만든 quu 모듈을 main.rs 에서 사용해보자.

mod quu;

fn main() {
    quu::public_quu();
}

별도의 파일이던 것과 비슷하게 사용할 수 있었다.

결과적으로 별도의 디렉터리로 분리했을 때 대표 파일의 이름이 mod.rs 여야 한다는 점이 규칙이며, 그 외에는 자유로운 것 같다.

모듈을 좀 더 간단하게 사용하기

제목을 짓기가 좀 힘들었는데, use 커맨드를 설명하기 위함이다. 앞서 언급된 예제의 일부를 다시 살펴보자.

mod foo;

fn main() {
    foo::bar::public_bar();
}

여기서 foo::bar::public_bar() 라는 함수를 좀 더 짧게 사용하려면 아래와 같이 use 를 사용할 수 있다.

mod foo;
use foo::bar;

fn main() {
    bar::public_bar();
}

use foo::bar 라는 명령의 의미는 foo 안의 bar 라는 모듈을 현재 네임스페이스에 병합하겠다는 의미다. 따라서 bar 모듈을 바로 액세스하는 것이 가능해진다.

물론 아래와 같이 좀 더 줄이는 것도 가능하다.

mod foo;
use foo::bar::public_bar;

fn main() {
    public_bar();
}

아예 bar 모듈의 특정 함수를 가져와버렸다. 당연하게도 이렇게 해서 이름이 겹치지 않는다면 이렇게 쓰는 것이 코드를 줄일 수 있다는 점에서 좋을 수도 있다.

만약 가져올 함수가 많다면 아래처럼 와일드카드(?)를 동원할 수도 있다.

use foo::bar::*;

이렇게 하면 foo 모듈 안의 bar 모듈 안의 모든 공개된 함수들을 현재 네임스페이스에서 쓸 수 있게 된다.

다만 이렇게 모든 이름을 가져와버리면 아무래도 이름이 겹쳐지는 사태가 발생할 확률이 커지므로 상황에 맞게 잘 활용해야 할 것이다.

부모 디렉터리 모듈에 접근하기

사실 좋은 설계는 아니겠지만, 자신 모듈의 구현에서 부모 모듈의 함수를 호출하는 경우가 있을 수도 있다. 이럴 때는 super 를 통해 접근하는 방법이 있다.

먼저 지금까지의 프로젝트 구조가 어떻게 되어있는지 다시 확인해보자.

src/
    main.rs
    foo.rs
    quu/
        mod.rs

이제 quu 라는 모듈에서 foo 안의 함수를 접근해보자. 아래는 quu 모듈에 새 함수 public_deep_quu 함수를 구현하면서 앞서 구현한 foo 모듈의 함수를 사용하는 예제다.

pub fn public_quu() {
    println!("public_quu!")
}

use super::foo;
pub fn public_deep_quu() {
    foo::public_foo();
}

이렇게 부모 모듈의 네임스페이스 또한 use 를 통해서 간섭하는 것이 가능하다.

이런 식으로 쓸 수 있다는 것이지 그다지 권장할 만한 기능은 아닐지도 모른다. 하지만 세상사가 그렇게 생각하는 대로 흘러가기만 하는 것은 아니라는 것도 인정해야 할 것 같다.