가시성과 프라이버시

가시성과 프라이버시 (Visibility and privacy)

"이 아이템을 여기에서 써도 되나요?" — 이 질문에 답하는 개념이 가시성(visibility)프라이버시(privacy) 예요. 이 두 용어는 거의 같은 의미로 쓰이는데, 핵심은 "이 아이템을 이 위치에서 사용할 수 있는가" 이죠. 아이템을 공개할지 숨길지를 정하는 기준을 살펴볼게요.

출처: Rust Reference

본문

문법 (Syntax)

Visibility → pub
           | pub ( crate )
           | pub ( self )
           | pub ( super )
           | pub ( in SimplePath )

이름 계층 구조 (Name hierarchy)

Rust의 이름 해석은 네임스페이스의 전역 계층 구조(global hierarchy) 위에서 동작해요. 계층의 각 단계는 어떤 아이템으로 볼 수 있어요. 그 아이템들은 앞서 언급한 것들에 더해 외부 크레이트도 포함해요. 새 모듈을 선언하거나 정의하는 일은, 그 정의 위치에 계층 구조에 새 트리를 삽입하는 것으로 생각할 수 있어요.

프라이버시 (Privacy)

인터페이스가 다른 모듈에서 사용될 수 있는지 제어하기 위해, Rust는 아이템의 각 사용을 검사해 허용 여부를 결정해요. 바로 이 지점에서 프라이버시 경고가 생성돼요. 거칠게 말하면 "다른 모듈의 비공개 아이템을 허용 없이 썼다"는 것이죠.

기본 가시성 (Default)

기본적으로 모든 것은 비공개(private) 예요. 다만 예외가 두 가지 있어요.

  • pub 트레이트의 연관 아이템은 기본적으로 공개예요.
  • pub 열거형의 enum 변형도 기본적으로 공개예요.

아이템이 pub 로 선언되면 바깥 세상에서 접근할 수 있다고 생각하면 돼요. 예를 들어요.

fn main() {}
// Declare a private struct
struct Foo;

// Declare a public struct with a private field
pub struct Bar {
    field: i32,
}

// Declare a public enum with two public variants
pub enum State {
    PubliclyAccessibleState,
    PubliclyAccessibleState2,
}

Foo 는 기본이 비공개라 이 모듈에서만 쓸 수 있고, Bar 는 공개지만 필드 field 는 비공개예요. State 의 변형은 공개라서 바깥에서 접근할 수 있어요.

접근 규칙 (Access)

아이템이 공개냐 비공개냐를 바탕으로, Rust는 두 가지 경우에만 아이템 접근을 허용해요.

  • 공개 아이템: 어떤 모듈 m 에서, 그 아이템의 모든 조상(ancestor) 모듈에 접근할 수 있다면 m 밖에서도 접근할 수 있어요. 재수출(re-export)을 통해 이름을 붙일 수도 있는데, 이건 아래에서 다뤄요.
  • 비공개 아이템: 현재 모듈과 그 하위(descendant) 모듈에서 접근할 수 있어요.

이 두 규칙은 모듈 계층을 만들면서 공개 API를 드러내고 내부 구현을 숨기는 데 놀랍도록 유용해요. 몇 가지 흔한 상황을 볼게요.

  • 라이브러리 개발자: 자신의 라이브러리를 링크하는 크레이트에 기능을 노출해야 해요. 첫 번째 규칙에 따라, 외부에서 쓸 수 있는 것은 루트부터 대상 아이템까지 전부 pub 이어야 해요. 체인 중 어느 하나라도 비공개면 외부 접근이 막혀요.
  • 전역 헬퍼 모듈: 크레이트 자체에서만 쓸 전역 "헬퍼 모듈"이 필요한데, 공개 API로는 노출하고 싶지 않을 때. 이 경우 크레이트 계층의 루트에 비공개 모듈을 두고, 그 안에 "공개 API"를 두면 돼요. 크레이트 전체가 루트의 하위이므로 두 번째 규칙에 따라 로컬 크레이트 전체가 그 비공개 모듈에 접근할 수 있어요.
  • 단위 테스트: 테스트를 쓸 때 테스트 대상 모듈의 바로 아래 자식으로 mod test 를 두는 흔한 관용구가 있어요. 이 모듈은 두 번째 규칙에 따라 부모 모듈의 어떤 아이템이든 접근할 수 있어서, 내부 구현 세부 사항까지 자식 모듈에서 매끄럽게 테스트할 수 있어요.

두 번째 규칙에서 비공개 아이템을 "접근할 수 있다"고 했는데, 아이템의 접근의 정확한 의미는 아이템이 무엇이냐에 따라 달라요. 모듈에 접근한다는 건 그 안을 들여다보는(항목을 더 가져오기 위해) 것을 뜻해요. 반면 함수에 접근한다는 건 그 함수를 호출한다는 뜻이죠. 게다가 경로 표현식과 import 문도, 목적지가 현재 가시성 스코프 안에 있을 때만 유효하다는 점에서 아이템에 접근하는 것으로 간주돼요.

위 세 가지 상황을 모두 보여 주는 예시를 볼게요.

// This module is private, meaning that no external crate can access this
// module. Because it is private at the root of this current crate, however, any
// module in the crate may access any publicly visible item in this module.
mod crate_helper_module {

    // This function can be used by anything in the current crate
    pub fn crate_helper() {}

    // This function *cannot* be used by anything else in the crate. It is not
    // publicly visible outside of the `crate_helper_module`, so only this
    // current module and its descendants may access it.
    fn implementation_detail() {}
}

// This function is "public to the root" meaning that it's available to external
// crates linking against this one.
pub fn public_api() {}

// Similarly to 'public_api', this module is public so external crates may look
// inside of it.
pub mod submodule {
    use crate::crate_helper_module;

    pub fn my_method() {
        // Any item in the local crate may invoke the helper module's public
        // interface through a combination of the two rules above.
        crate_helper_module::crate_helper();
    }

    // This function is hidden to any module which is not a descendant of
    // `submodule`
    fn my_implementation() {}

    #[cfg(test)]
    mod test {

        #[test]
        fn test_my_implementation() {
            // Because this module is a descendant of `submodule`, it's allowed
            // to access private items inside of `submodule` without a privacy
            // violation.
            super::my_implementation();
        }
    }
}

fn main() {}

핵심을 짚어 보면, crate_helper_module 은 비공개 모듈이라 바깥 크레이트에서 접근할 수 없지만, 크레이트 안의 어떤 모듈이든 그 안의 pub fn crate_helper 를 쓸 수 있어요. fn implementation_detail 은 그 모듈과 하위에서만 접근 가능하고요. submodulemod testsubmodule 의 하위라서 비공개인 my_implementation 을 프라이버시 위반 없이 super::my_implementation() 로 호출해요.

Rust 프로그램이 프라이버시 검사(privacy checking)를 통과하려면, 위 두 규칙에 따라 모든 경로가 유효한 접근이어야 해요. 여기에는 모든 use 문, 표현식, 타입 등이 포함돼요.

pub(in path), pub(crate), pub(super), pub(self)

공개·비공개에 더해, Rust는 아이템을 주어진 스코프 안에서만 보이도록 선언할 수도 있어요. pub 제한의 규칙은 다음과 같아요.

  • pub(in path) — 아이템을 주어진 path 안에서 보이게 해요. path 는 가시성을 선언하는 아이템의 조상 모듈로 해석되는 단순 경로(simple path)여야 해요. path 의 각 식별자는 use 문이 도입한 이름이 아니라 직접 모듈을 가리켜야 해요.
  • pub(crate) — 아이템을 현재 크레이트 안에서 보이게 해요.
  • pub(super) — 아이템을 부모 모듈에서 보이게 해요. pub(in super) 와 동일해요.
  • pub(self) — 아이템을 현재 모듈에서 보이게 해요. pub(in self) 또는 pub 을 아예 쓰지 않는 것과 동일해요.

예시를 볼게요.

pub mod outer_mod {
    pub mod inner_mod {
        // This function is visible within `outer_mod`
        pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
        // Same as above, this is only valid in the 2015 edition.
        pub(in outer_mod) fn outer_mod_visible_fn_2015() {}

        // This function is visible to the entire crate
        pub(crate) fn crate_visible_fn() {}

        // This function is visible within `outer_mod`
        pub(super) fn super_mod_visible_fn() {
            // This function is visible since we're in the same `mod`
            inner_mod_visible_fn();
        }

        // This function is visible only within `inner_mod`,
        // which is the same as leaving it private.
        pub(self) fn inner_mod_visible_fn() {}
    }
    pub fn foo() {
        inner_mod::outer_mod_visible_fn();
        inner_mod::crate_visible_fn();
        inner_mod::super_mod_visible_fn();

        // This function is no longer visible since we're outside of `inner_mod`
        // Error! `inner_mod_visible_fn` is private
        //inner_mod::inner_mod_visible_fn();
    }
}

fn bar() {
    // This function is still visible since we're in the same crate
    outer_mod::inner_mod::crate_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `super_mod_visible_fn` is private
    //outer_mod::inner_mod::super_mod_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `outer_mod_visible_fn` is private
    //outer_mod::inner_mod::outer_mod_visible_fn();

    outer_mod::foo();
}

fn main() { bar() }

이 예시에서 mod bar(루트 수준)는 crate_visible_fn 만 같은 크레이트라 접근 가능하고, outer_mod 밖이라 outer_mod_visible_fnsuper_mod_visible_fn 은 접근할 수 없어요.

참고: 이 문법은 아이템 가시성에 제한을 하나 더 추가할 뿐이에요. 아이템이 지정된 스코프의 모든 부분에서 보이는 것을 보장하지 않아요. 아이템에 접근하려면, 현재 스코프까지의 모든 부모 아이템도 계속 보여야 해요.

2018 에디션 차이: 2018 에디션부터 pub(in path) 의 경로는 crate, self, super 로 시작해야 해요. 2015 에디션에서는 :: 로 시작하거나 크레이트 루트의 모듈로 시작하는 경로도 쓸 수 있었어요.

재수출과 가시성 (Re-exporting and visibility)

Rust는 pub use 지시문으로 아이템을 공개 재수출(publicly re-export) 할 수 있어요. 이는 공개 지시문이기 때문에, 위 규칙들로 현재 모듈에서 그 아이템을 사용할 수 있게 해 줘요. 본질적으로 재수출된 아이템으로의 공개 접근을 허용하는 거죠. 다음 프로그램은 유효해요.

pub use self::implementation::api;

mod implementation {
    pub mod api {
        pub fn f() {}
    }
}

fn main() {}

여기서 외부 크레이트가 implementation::api::f 를 참조하면 프라이버시 위반이 되지만, api::f 경로는 허용돼요.

비공개 아이템을 재수출할 때는, 평소처럼 네임스페이스 계층을 통과하는 대신 재수출을 통해 "프라이버시 체인(privacy chain)"을 단락(short-circuit) 시키는 것으로 생각할 수 있어요.

더 알아보기 (Learn more)