RustのVoldemort typesについて
RustにはVoldemort types(ヴォルデモート型)と呼ばれるものがある。(lintの暫定的な名前はunnameable_types
)
簡単に言うと、プライベートなモジュールの中で定義されているパブリックな型のこと。
mod m {
// `m::super`の外でこの名前を使うことはできない
pub struct Voldemort;
}
// 外部クレートは`m::Voldemort`にアクセスする手段がない
// しかし、`Voldemort`は`pub`なので戻り値として返すことは可能
pub fn get_voldemort() -> m::Voldemort {
m::Voldemort
}
外部クレートのget_voldemort
関数を呼ぶことは許されているので、以下のように書くことができる。
mod external;
fn main() {
let wizard = external::get_voldemort();
}
この時、wizard
変数はexternal::m::Voldemort
型になるが、m
モジュールの可視性がpub
ではないため、型名を明示することはできない。
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
型にアクセスできない旨のメッセージを表示してくれる。
それなら、複数モジュールに同名の型を定義した場合はどうなるのだろうか?
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
m1
、m2
ともに候補に出してくれた。コンパイラ賢い。
型名を明示する方法は?
ありません。
しかし、ジェネリクスを駆使して、構造体フィールドや関数の定義はできます。
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
や型エイリアスを定義してあげるのが良いと思います。こうすることで、外部から使うときには意識させるべきでないモジュール構造を隠蔽することができる。
mod m {
pub struct Voldemort;
}
pub use m::Voldemort;
// またはこっち
// pub type Voldemort = m::Voldemort;
pub fn get_voldemort() -> Voldemort {
m::Voldemort
}
mod external;
fn main() {
let _: external::Voldemort = external::get_voldemort();
}
型名が分からなくても(明示できなくても)インスタンスを作れる
隠ぺいされた構造体がDefault
トレイトを実装していると、型推論によりインスタンスを宣言することが可能。
mod m {
#[derive(Default)]
pub struct Voldemort;
}
pub fn get_voldemort() -> m::Voldemort {
m::Voldemort
}
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