Reinventing Square Wheels - algon's blog

Rustのunitみたいな型

unitっぽい型について書きます。

unit型

Rustにはただ一つの値だけをもつunit型があります。 結果を返さない関数の戻り値の型として登場します。 型名・値どちらも()と書きます。

unit-like構造体

「フィールドを持たない構造体」を独自に定義してunitのように使うこともできます。 このような構造体はunit-like structと呼ばれることがあります。 トレイトを実装したりメソッドを追加できる点でunit型と異なります。

 1struct Unitlike1;
 2struct Unitlike2();
 3struct Unitlike3{}
 4
 5fn foo(_: Unitlike1) {}
 6
 7fn main() {
 8    let u1: Unitlike1 = Unitlike1;
 9    let u2: Unitlike2 = Unitlike2();
10    let u3: Unitlike3 = Unitlike3{};
11
12    foo(u1);  // OK
13//  foo(u2);  // Error
14//  foo(());  // Error
15}

上の例では3つの構造体を定義していますが、これらは互いに異なる型になります。 当たり前ですが、Unitlike1の値を引数にとる関数にUnitlike2()を渡すことはできません。

ちなみにUnilike2のようにタプル構造体の形式で定義すると、 「コンストラクタの関数ポインタ」を得ることができるようです。 enumのタプル形式のバリアントでも同様にコンストラクタを得ることができ、たまに役に立ちます。

1struct Unitlike2();
2
3fn main() {
4    let u2_ctor: fn() -> Unitlike2 = Unitlike2;
5    let u2: Unitlike2 = u2_ctor();
6}

unitを含む構造体

更に似たような構造体として「unit型の値のみを持つ構造体」があります。

1struct Unitlike4(());
2
3fn main() {
4    let u4 = Unitlike4(());
5}

Unitlike4Unitlike2と似ていますが、コンストラクタが公開されない点が異なります。 別のモジュールで定義されている場合、pub structとして型自体が公開されていても、 外部からUnitlike4の値を作ることはできません。

 1mod unit {
 2    pub struct Unitlike2();
 3
 4    pub struct Unitlike4(());
 5    pub fn new_u4() -> Unitlike4 {
 6        Unitlike4(())
 7    }
 8
 9    pub struct Unitlike5(pub ());
10}
11
12fn main() {
13    let u2 = unit::Unitlike2();    // OK
14//  let u4 = unit::Unitlike4(());  // Error
15    let u4 = unit::new_u4();       // OK
16    let u5 = unit::Unitlike5(());  // OK
17}

Unitlike5のようにフィールドをpubにすれば、 モジュールの外部から作ることができるようになります。

あえてUnitlike4のようにコンストラクタを隠して定義し、 意図的にCloneトレイトを実装しないことで、 事前条件を静的にチェックするような使い方ができそうです。

 1mod some_module {
 2    pub struct F1Called(());
 3
 4    pub fn f1() -> F1Called {
 5        F1Called(())
 6    }
 7
 8    pub fn f2(_: F1Called) {
 9        println!("必ずf1が先に呼ばれているはず!");
10    }
11}
12
13fn main() {
14    let token = some_module::f1();
15    some_module::f2(token);  // f1を呼ばずにf2を呼ぶことはできない
16}

空のenum

空のenumを用いて、値を持たない型を定義できます。 コンパイル時にのみ使われる型レベルのタグとして用いることができます。

1enum Empty {}

まとめ

 1mod unit {
 2    pub struct A;
 3    pub struct B();
 4    pub struct C(());
 5    pub struct Cp(pub ());
 6    pub struct D {}
 7    pub struct E { e: () }
 8    pub struct Ep { pub e: () }
 9    pub enum F {}
10}
11
12fn main() {
13    let a = unit::A;
14    let b = unit::B();
15//  let c = unit::C(()); // Error
16    let c = unit::Cp(());
17    let d = unit::D {};
18//  let e = unit::E { e: () }; // Error
19    let e = unit::Ep { e: () };
20//  let f = F::???;  // ムリ
21}