Rust の型に関する覚え書き
この記事では Rust の型(type)に関することを思いつくまま脈絡なく書き記しておく。 随時更新予定。
組み込みデータ型
組み込みデータ型には大きく分けてスカラ型と複合型がある。
スカラ型
スカラ型はスカラ値を有する型である。 型によって値のサイズが決まっていて,受け渡しは copy semantics で行われる。
スカラ型に分類される型は以下の通り。
1. 整数型
サイズ | 符号あり | 符号なし | 既定 |
---|---|---|---|
8 bits | i8 |
u8 |
|
16 bits | i16 |
u16 |
|
32 bits | i32 |
u32 |
|
64 bits | i64 |
u64 |
|
— | isize |
usize |
isize
/usize
のサイズはアーキテクチャに依存する1。
また,リテラル表記時等での型推測の既定は i32
型となる2。
let x = 123; //type i32
ただしリテラル表記の後ろに型名を付けることで型を指定することが可能。
let x = 123i64; //type i64
もちろん変数に型注釈を付けて明示することも可能。
let x: u8 = 123; //type u8
2. 浮動小数点数型
サイズ | 型名 | 既定 |
---|---|---|
32 bits (仮数部 23 bits) | f32 |
|
64 bits (仮数部 52 bits) | f64 |
型の推定・評価は整数型と同じで,リテラル表記時等での型推測の既定は f64
型となる3。
let x = 1.23; //type f64
3. 文字型
文字型 char
は Unicode 符号点を示す。
リテラル表記はこんな感じ。
let c = '♡';
Rust における文字列と文字の関係については「Rust の文字列操作(1)」を参照のこと。
4. 論理値型
論理値型 bool
は true
と false
の2値のみ取り得る。
複合型
複合型は複数の要素を組み合わせた型で,タプル型と配列型がある。
複合型は定義毎に型と要素数が決まっている。 要素の型が全てスカラ型であれば copy semantics で値の受け渡しが可能。
1. タプル型
タプル型は(名前の通り)複数の型を組み合わせた型で,こんな感じに記述できる。
fn main() {
let tup = (500, 6.4, 1u8);
println!("tup = {:?}", tup); //Output: tup = (500, 6.4, 1)
}
2. 配列型
配列型は,単一の型で構成される複数要素の型で4,こんな感じに記述できる。
fn main() {
let ary = [1, 2, 3];
println!("ary = {:?}", ary); //Output: ary = [1, 2, 3]
}
型エイリアス
型の別名定義。 名前は変わっても機能は変わらない。 総称型と組み合わせると吉?
type Strings = Vec<String>;
fn main() {
let planets: Strings = vec![
"Mercury".to_string(),
"Venus".to_string(),
"Earth".to_string(),
"Mars".to_string(),
];
planets.iter().for_each(|p| {
println!("{}", p);
});
}
構造体
- 0個以上のフィールド(型と名前)を含むデータ構造
- 構造体に紐づく関連関数およびメソッドを実装可能
- トレイトからの実現(realization)が可能。構造体間の継承(inheritance)は不可
struct Planet {
name: String,
mass: f64,
distance: f64,
}
構造体のインスタンス化
リテラル表現を使ったインスタンス化。
#[derive(Debug)]
struct Planet {
name: String,
mass: f64,
distance: f64,
}
fn main() {
let p = Planet {
name: "Earth".to_string(),
mass: 1.0,
distance: 1.0,
};
println!("{:?}", p); //Output: Planet { name: "Earth", mass: 1.0, distance: 1.0 }
}
インスタンス化関数を実装してみる。
#[derive(Debug)]
struct Planet {
name: String,
mass: f64,
distance: f64,
}
impl Planet {
fn new(s: &str, mass: f64, distance: f64) -> Planet {
Planet {
name: s.to_string(),
mass,
distance,
}
}
}
fn main() {
let p = Planet::new("Earth", 1.0, 1.0);
println!("{:?}", p); //Output: Planet { name: "Earth", mass: 1.0, distance: 1.0 }
}
構造体のコピー
コピー用のメソッドを書いてみた。
impl Planet {
fn copy(&self) -> Planet {
Planet {
name: self.name.clone(),
..*self
}
}
}
あるいは derive
構文を使って Clone
トレイトから clone()
メソッドを自動生成する。
#[derive(Debug, Clone)]
struct Planet {
name: String,
mass: f64,
distance: f64,
}
fn main() {
let p = Planet::new("Earth", 1.0, 1.0);
let cpy = p.clone();
println!("{:?}", p); //Output: Planet { name: "Earth", mass: 1.0, distance: 1.0 }
println!("{:?}", cpy); //Output: Planet { name: "Earth", mass: 1.0, distance: 1.0 }
}
なお String
等の Copy
トレイトを実装できないフィールドを含む構造体は Copy
トレイトを実装できないため,代入構文で値のコピーが発生しない。
この場合は move semantics により所有権が移動する。
タプル構造体
構造体の特殊なパターンで,名前のないフィールドを定義する。 フィールドがタプルで定義される以外は通常の構造体と同じ。 関連関数およびメソッドも実装可能。
#[derive(Debug, Copy, Clone)]
struct Point(i64, i64);
impl Point {
fn new(x: i64, y: i64) -> Point {
Point(x, y)
}
}
fn main() {
let p1 = Point(1, 2);
let p2 = Point::new(3, 4);
let p3 = p1; //copy semantics
println!("{:?}", p1); //Output: PPoint(1, 2)
println!("{:?}", p2); //Output: PPoint(3, 4)
println!("{:?}", p3); //Output: PPoint(1, 2)
}
列挙型
Rust の列挙型 enum
はどちらかというと関数型プログラミング言語の影響を強く受けているらしい。
enum VariantType {
Int(i32),
Float(f64),
Text(String),
}
列挙型の評価には match
式を用いる。
fn main() {
let list: Vec<VariantType> = vec![
VariantType::Int(123),
VariantType::Float(1.23),
VariantType::Text("hello".to_string()),
];
list.iter().for_each(|e| {
match e {
VariantType::Int(v) => println!("{:#x}", v), //Output: 0x7b
VariantType::Float(v) => println!("{:e}", v), //Output: 1.23e0
VariantType::Text(v) => println!("{:?}", v), //Output: "hello"
};
});
}
列挙型も構造体と同じく関連関数やメソッドを実装できる。
impl VariantType {
fn is_integer(&self) -> bool {
match self {
VariantType::Int(_) => true,
_ => false, //others
}
}
}
fn main() {
let list = vec![
VariantType::Int(123),
VariantType::Float(1.23),
VariantType::Text("hello".to_string()),
];
list.iter().for_each(|e| {
println!("Is {:?} an integer?: {}", e, e.is_integer());
});
}
総称型
Rust ではオブジェクトの抽象化の手段として総称型をサポートしている。
関数における総称型
以下の max()
関数で使われる型 T
が総称型と呼ばれているもの。
fn max<T: std::cmp::PartialOrd>(left: T, right: T) -> T {
if left >= right {
left
} else {
right
}
}
fn main() {
let x = 1;
let y = 2;
println!("max({}, {}) = {}", x, y, max(x, y)); //Output: max(1, 2) = 2
}
<T: std::cmp::PartialOrd>
の表現は型 T
の制約条件を示すもので std::cmp::PartialOrd
トレイトを実装する型のみ max()
関数が有効になる。
このようなトレイトを使った制約の定義を「トレイト境界(trait bound)」と呼ぶらしい。
トレイト境界は where
節を使って記述することもできる。
fn max<T>(left: T, right: T) -> T
where
T: std::cmp::PartialOrd,
{
if left >= right {
left
} else {
right
}
}
トレイト境界の記述は煩雑になりがちなので,これで多少はスッキリするだろう。
ちなみに std::cmp::PartialOrd
トレイトは >
, >=
, <
, <=
の順序比較演算子を使うためのもので,少なくとも組み込みデータ型は全て std::cmp::PartialOrd
トレイトを実装している。
たとえば比較可能でない構造体
#[derive(Debug)]
struct Person {
age: u32,
name: String,
}
に max()
関数を使おうとしても
fn max<'a, T: std::cmp::PartialOrd>(left: &'a T, right: &'a T) -> &'a T {
if left >= right {
left
} else {
right
}
}
fn main() {
let p1 = Person {
age: 24,
name: "Alice".to_string(),
};
let p2 = Person {
age: 24,
name: "Bob".to_string(),
};
println!("max({:?}, {:?}) = {:?}", p1, p2, max(&p1, &p2)); //Error: can't compare `Person` with `Person`
}
コンパイルエラーになる。
構造体における総称型
構造体のフィールドやメソッドも総称型で記述することができる。
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: std::ops::Add<Output = T> + Copy> Point<T> {
fn add(&self, p: &Self) -> Self {
Self::new(self.x + p.x, self.y + p.y)
}
}
impl<T: std::fmt::Display> std::fmt::Display for Point<T> {
fn fmt(&self, dest: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(dest, "<{}, {}>", self.x, self.y)
}
}
fn main() {
let p1 = Point::new(1, 2);
let p2 = Point::new(3, 4);
println!("{} + {} = {}", p1, p2, p1.add(&p2)); //Output: <1, 2>
}
上述のように impl
構文毎に個別にトレイト境界を設定できる。
列挙型における総称型
列挙型も構造体と同じように総称型が使える。
列挙型と総称型の組み合わせでもっとも有名なのが Result
型だろう。
enum Result<T, E> {
Ok(T),
Err(E),
}
参考図書
- プログラミング言語Rust 公式ガイド
- Steve Klabnik (著), Carol Nichols (著), 尾崎 亮太 (翻訳)
- KADOKAWA 2019-06-28 (Release 2019-06-28)
- 単行本
- 4048930702 (ASIN), 9784048930703 (EAN), 4048930702 (ISBN)
- 評価
公式ドキュメントの日本語版。索引がちゃんとしているので,紙の本を買っておいて手元に置いておくのが吉。
- プログラミングRust
- Jim Blandy (著), Jason Orendorff (著), 中田 秀基 (翻訳)
- オライリージャパン 2018-08-10
- 単行本(ソフトカバー)
- 4873118557 (ASIN), 9784873118550 (EAN), 4873118557 (ISBN)
- 評価
Eブック版あり。公式ドキュメントよりも系統的に書かれているので痒いところに手が届く感じ。ただし量が多いので,一度斜め読みしたらあとは傍らに置いて必要に応じてつまみ食いしていくのがいいだろう。