😈

RustのVoldemort typesについて

2023/01/08に公開

RustにはVoldemort types(ヴォルデモート型)と呼ばれるものがある。(lintの暫定的な名前はunnameable_types
簡単に言うと、プライベートなモジュールの中で定義されているパブリックな型のこと。

external.rs
mod m {
    // `m::super`の外でこの名前を使うことはできない
    pub struct Voldemort;
}

// 外部クレートは`m::Voldemort`にアクセスする手段がない
// しかし、`Voldemort`は`pub`なので戻り値として返すことは可能
pub fn get_voldemort() -> m::Voldemort {
    m::Voldemort
}

外部クレートのget_voldemort関数を呼ぶことは許されているので、以下のように書くことができる。

main.rs
mod external;

fn main() {
    let wizard = external::get_voldemort();
}

この時、wizard変数はexternal::m::Voldemort型になるが、mモジュールの可視性がpubではないため、型名を明示することはできない。

main.rs
mod external;

fn main() {
    let wizard: external::m::Voldemort = external::get_voldemort();
}
$ cargo run
error[E0603]: module `m` is private
 --> src/main.rs:4:27
  |
4 |     let wizard: external::m::Voldemort = external::get_voldemort();
  |                           ^ private module
  |
note: the module `m` is defined here
 --> src/external.rs:1:1
  |
1 | mod m {
  | ^^^^^

For more information about this error, try `rustc --explain E0603`.
error: could not compile `voldemort` due to previous error
因みに、`m`モジュール名を省略(`external::Voldemort`)してみると、エラーメッセージが変わる
$ cargo run
error[E0412]: cannot find type `Voldemort` in module `external`
 --> src/main.rs:4:27
  |
4 |     let wizard: external::Voldemort = external::get_voldemort();
  |                           ^^^^^^^^^ not found in `external`
  |
note: struct `crate::external::m::Voldemort` exists but is inaccessible
 --> src/external.rs:2:5
  |
2 |     pub struct Voldemort;
  |     ^^^^^^^^^^^^^^^^^^^^^ not accessible

For more information about this error, try `rustc --explain E0412`.
error: could not compile `voldemort` due to previous error

mモジュールの中のVoldemort型にアクセスできない旨のメッセージを表示してくれる。
それなら、複数モジュールに同名の型を定義した場合はどうなるのだろうか?

external.rs
mod m1 {
    pub struct Voldemort;
}
mod m2 {
    pub struct Voldemort;
}

pub fn get_voldemort() -> m1::Voldemort {
    m1::Voldemort
}
$ cargo run
error[E0412]: cannot find type `Voldemort` in module `external`
 --> src/main.rs:4:27
  |
4 |     let wizard: external::Voldemort = external::get_voldemort();
  |                           ^^^^^^^^^ not found in `external`
  |
note: these structs exist but are inaccessible
 --> src/external.rs:2:5
  |
2 |     pub struct Voldemort;
  |     ^^^^^^^^^^^^^^^^^^^^^ `crate::external::m1::Voldemort`: not accessible
...
5 |     pub struct Voldemort;
  |     ^^^^^^^^^^^^^^^^^^^^^ `crate::external::m2::Voldemort`: not accessible

For more information about this error, try `rustc --explain E0412`.
error: could not compile `voldemort` due to previous error

m1m2ともに候補に出してくれた。コンパイラ賢い。

型名を明示する方法は?

ありません。
しかし、ジェネリクスを駆使して、構造体フィールドや関数の定義はできます。

mod external;

struct S<T> {
    vol: T,
}

fn type_of<T>(_: T) {
    println!("{}", std::any::type_name::<T>());  // {crate名}::external::m::Voldemort
}

fn main() {
    let _ = S { vol: external::get_voldemort() };
    let wizard = external::get_voldemort();
    type_of(wizard);
}

それじゃあ扱いにくくない?

そもそも、ヴォルデモート型というのは、型をカプセル化することでインスタンスを作らせないようにする能力がある。
それを無視する場合、クレート開発者は、外部からアクセスできる領域にpub useや型エイリアスを定義してあげるのが良いと思います。こうすることで、外部から使うときには意識させるべきでないモジュール構造を隠蔽することができる。

extern.rs
mod m {
    pub struct Voldemort;
}

pub use m::Voldemort;
// またはこっち
// pub type Voldemort = m::Voldemort;

pub fn get_voldemort() -> Voldemort {
    m::Voldemort
}
main.rs
mod external;

fn main() {
    let _: external::Voldemort = external::get_voldemort();
}

型名が分からなくても(明示できなくても)インスタンスを作れる

隠ぺいされた構造体がDefaultトレイトを実装していると、型推論によりインスタンスを宣言することが可能。

external.rs
mod m {
    #[derive(Default)]
    pub struct Voldemort;
}

pub fn get_voldemort() -> m::Voldemort {
    m::Voldemort
}
main.rs
mod external;

fn main() {
    let a = Default::default();
    let b = external::get_voldemort();
    let mut c;
    c = &a;
    c = &b;
}

この時、変数aの中身は期待するものでない可能性があるので注意したい。

`Voldemort`型にプライベートなフィールドを持たせても無駄である

Defaultトレイトはプライベートなフィールドが含まれている構造体において、外部から初期化できないようになっている。
そのため、以下はコンパイルエラーとなる。

mod m {
    #[derive(Default)]
    pub struct S {
        pub a: i32,
            b: i32,
    }
}
fn main() {
    let s = m::S {
        a: 1,
        ..Default::default()
    };
}
error[E0451]: field `b` of struct `S` is private
  --> src/main.rs:11:11
   |
11 |         ..Default::default()
   |           ^^^^^^^^^^^^^^^^^^ field `b` is private

それなら、ヴォルデモート型にもダミーのプライベートフィールドを持たせれば良いのでは?
と思うが、以下の書き方はコンパイルできてしまうので、不正解🙅

let s = m::S::default();

まとめ

到達可能な可視性だけど、その型名はコードに書けないというもの。
型をカプセル化する仕組みとも捉えられる?

参考

Discussion