目次
- 19.3.0 型についての発展的事項の概要
- 19.3.1 型安全性と抽象化のためのニュータイプパターンの使用
- 19.3.2 型のエイリアスを使用して型の同義語を作成する
- 19.3.3 never 型は絶対に返らない
-
19.3.4 動的サイズ型と
Sized
トレイト
19.3.0 型についての発展的事項の概要
-
ニュータイプパターンが有用な理由
- 型をつけることで異なる種類の値同士が混同されることを防ぐことができる
- 例えば、
Meters(u32)
とMillmeters(u32)
を混同せずに済む
- 例えば、
- 型の実装の詳細を抽象化することもできる
- 例えば、ID と人名を関連付けて格納する
HashMap<i32, String>
をPeople
型でラップすることで以下のような恩恵が得られる-
People
コレクションにメソッドを適切に定義することで、HashMap
の提供するすべてのAPI ではなく、自分たちの定義したパブリックな API とだけやり取りするようにカプセル化を施すことができる
-
- 例えば、ID と人名を関連付けて格納する
- 型をつけることで異なる種類の値同士が混同されることを防ぐことができる
-
型エイリアス
- 型に "あだ名" をつけることができる
- たとえば
type Kilometers = i32;
とすると、i32
型を指す際にKilometers
とすることができる - これは、複雑な型をコード内で何度も使用する必要があるときに有用
- たとえば
type Thunk = Box<dyn Fn() + Send + 'static>;
のようにすると便利
- たとえば
-
never 型
!
-
todo!()
、panic!()
や ループ中のbreak;
とcontinue;
、他にはbreak;
を含まないloop{...}
などは never 型!
を返す - この
!
はどのような型にも型強制される- →
match
式の一部のアームで使用した際に、一見すると型が食い違う場合も問題なく型強制される
- →
-
-
動的サイズ決定型はコンパイル時にメモリサイズが既知でない値を扱うための型
- 例は
str
型とすべてのトレイト - これらの型は、適当なポインタと組み合わせて使用する必要がある
- たとえば、
&str
やBox<dyn Trait>
のように
- たとえば、
- 例は
-
Rust ではコンパイル時にメモリサイズが既知の値をあらわすトレイト
Sized
が提供されている- このトレイトはデフォルトですべての要素にあてがわれる
- このデフォルトの処理を上書きしてジェネリック関数で動的サイズ決定型を使いたい場合は、
fn generic<T: ?Sized>(t: &T) {
のよう?Sized
トレイト境界を指定すればよい
19.3.1 型安全性と抽象化のためのニュータイプパターンの使用
→ 概要で述べた以上の情報なし
19.3.2 型のエイリアスを使用して型の同義語を作成する
-
Rustには、型エイリアスを宣言して既存の型に別の名前を付ける機能がある(=同義語を作成する)
-
これには
type
キーワードを使う -
ただし、型エイリアスを使用しても、ニュータイプパターンのように型の混同を防ぐことにはならないことに注意
- あくまで、既存の型の同義語を作る機能であって、新しい個別の型を作る機能ではない
- 新しい型を作りたいならニュータイプパターンなどを利用すること
-
例:
type Kilometers = i32; let x: i32 = 5; let y: Kilometers = 5; println!("x + y = {}", x + y); // i32 と Kilometers は同じ型なので問題なくコンパイルされ、実行もされる
-
この機能を使えば複雑で長ったらしい型名を何度も読み書きする必要がなくなる
-
例:
type Thunk = Box<dyn Fn() + Send + 'static>; let f: Thunk = Box::new(|| println!("hi")); fn takes_long_type(f: Thunk) { // --snip-- } fn returns_long_type() -> Thunk { // --snip-- }
-
-
この機能の最たる例は 標準ライブラリの
std::io
モジュールのResult<T, std::io::Error>
である-
標準ライブラリでは
type Result<T> = std::result::Result<T, std::io::Error>;
という型エイリアスが設定されている -
そのため、
Result<T>
と書くだけでResult<T, std::io::Error>
を表せる -
例:以下のコードは、さらにその下のコードのように書き換えられる
use std::fmt; use std::io::Error; pub trait Write { fn write(&mut self, buf: &[u8]) -> Result<usize, Error>; fn flush(&mut self) -> Result<(), Error>; fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>; fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>; }
use std::fmt; use std::io::Error; pub trait Write { fn write(&mut self, buf: &[u8]) -> Result<usize>; fn flush(&mut self) -> Result<()>; fn write_all(&mut self, buf: &[u8]) -> Result<()>; fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>; }
-
19.3.3 never 型は絶対に返らない
→ 概要で述べた以上の情報なし
19.3.4 動的サイズ型と Sized
トレイト
- 動的サイズ決定型(DST, unsized 型):
- 実行時にしかサイズを知ることができない値を使ったコードを書けるようにしてくれている
- これらの型は直接関数の引数に渡すことができない
-
&
,Box<...>
,Rc<...>
などを使ってポインタの背後に置いて使用する必要がある
-
- 最たる例は
str
型やトレイト
str
型について
-
str
型は動的サイズ決定型 -
たとえば、以下のようなコードは許されない:
- あきらかに、
s1
とs2
の長さ(必要なメモリ数)が異なる - Rust では、ある型の値はすべて同じ量のメモリを使わなければならないので、これは許されない
let s1: str = "Hello there!"; let s2: str = "How's it going?";
- あきらかに、
-
これは
str
を&str
にすることによって解消される-
&str
は通常の参照と異なり、ある値のメモリアドレスを格納するのみならず、文字列の長さも格納する- これが動的サイズ決定型
str
が通常の型と異なる点
- これが動的サイズ決定型
-
ある文字列の先頭へのポインタとその文字列の長さのみを格納するだけなので
&str
型はコンパイル時にメモリサイズがわかる
-
-
実は
str
はあらゆるポインタと組み合わせることができる- たとえば、
Box<str>
やRc<str>
などにすることもできる
- たとえば、
トレイトについて
- すべてのトレイトも動的サイズ決定型である
- トレイトも
str
と同様に、ポインタと組み合わせることで、型を指定するのに利用できる- たとえば、
&dyn Trait
,Box<dyn Trait>
,Rc<dyn Trait>
のように
- たとえば、
- 実は、トレイトオブジェクトがポインタと組み合わせて生成する必要があったのも、トレイトが動的サイズ決定型だからである
- トレイトも
Sized トレイトについて
-
Rust には、型のサイズがコンパイル時に既知かどうかを判断するための
Sized
トレイトが存在する -
このトレイトはコンパイル時にサイズが既知のすべてに対して自動的に実装される
-
さらに、Rust はすべてのジェネリック関数に
Sized
の境界を暗黙的に追加する-
つまり、以下のようなコードは、さらにその下のコードに置き換えられてコンパイルされると考えればよい
fn generic<T>(t: T) { // --snip-- }
fn generic<T: Sized>(t: T) { // --snip-- }
-
-
デフォルトでは、ジェネリック関数はコンパイル時に既知のサイズを持つ型に対してのみ機能する
-
しかし、以下の特別な構文を使用すればこの制限を緩和できる:
fn generic<T: ?Sized>(t: &T) { // --snip-- }
-
?Sized
というトレイト境界は、「TはSizedであってもなくてもよい」という意味を持つ -
また、
t
の型をT
から&T
に変更したことにも注意してほしい- 型が
Sized
でない可能性があるため、何らかのポインターの後ろで使う必要があるためこのようにしている
- 型が