記事について
次のTour of Rustを用いて学んだ内容のメモになります。
構造体
struct
によってフィールドの集合を記述できる。
(フィールドとはデータ構造とキーワードを紐付ける値で、値はプリミティブ型かデータ構造を指定できる)
構造体を定義することで、メモリ上で隣り合うデータ配置をコンパイラに伝えることができる。
struct SeaCreature {
// String は構造体である。
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
メソッド
メソッドは関数とは異なる。
メソッドはあるデータ型と紐づく関数のこと。
-
スタティックメソッド:ある型そのものに紐づき、演算子
::
で呼び出すことができる -
インスタンスメソッド:ある型のインスタンスに紐づき、演算子
.
で呼び出すことができる。
fn main() {
// スタティックメソッドでStringインスタンスを作成する。
let s = String::from("Hello world!");
// インスタンスを使ってメソッド呼び出す。
println!("{} is {} characters long.", s, s.len());
}
メモリ空間
Rustのプログラムは、3種類のメモリ空間を持っていて、データを保持している。
データメモリ
固定長もしくはスタティック(プログラムのライフサイクルで常に存在する)データを保持する。
プログラム内の文字列('Hello'等)は、読み取り専用なのでこの領域に入る。
コンパイラはこのようなデータに対してチューニングをしており、メモリ上の位置が固定ですでに知られているため非常に高速に扱うことができる。
スタックメモリ
関数内で宣言された変数を保持する。
関数が呼び出されている間は、メモリ上の位置が変更されないため、スタックメモリも非常に高速にデータにアクセスできる。
ヒープメモリ
プログラム実行時に生成されるデータを保持する。このメモリにあるデータは追加、移動、削除、サイズ変更が許されている。
動的であるため、遅いと思われがちですが、動的がゆえにメモリの使い方に柔軟性を持たせることができている。
データをヒープメモリに入れることをアロケーションといい、削除することをディアロケーションという。
構造体の扱い
構造体をインスタンス化すると、フィールドのデータをメモリ上で隣り合うように作成する。
すべてのフィールドの値を指定してインスタンス化する場合は構造体名 {...}
のように記述する。
構造体のフィールドへのアクセスは演算子.
を使用する。
struct SeaCreature {
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
// SeaCreatureのデータはスタックに入ります。
let ferris = SeaCreature {
// String構造体もスタックに入りますが、
// ヒープに入るデータの参照アドレスが一つ入ります。
animal_type: String::from("crab"),
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
let sarah = SeaCreature {
animal_type: String::from("octopus"),
name: String::from("Sarah"),
arms: 8,
legs: 0,
weapon: String::from("none"),
};
println!(
"{} is a {}. They have {} arms, {} legs, and a {} weapon",
ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
);
println!(
"{} is a {}. They have {} arms, and {} legs. They have no weapon..",
sarah.name, sarah.animal_type, sarah.arms, sarah.legs
);
}
コード例のメモリ状況について
- ダブルクオートに囲まれたテキスト("Ferris"等)は読み取り専用データであるため、データメモリに入る。
- 関数呼び出し
String::from
では構造体String
を作成し、この構造体は他のフィールドと隣り合うようにスタックメモリに入る。 - フィールドの値は変更可能であり、変更時はメモリ上で次のように変更される。
1. ヒープメモリに変更可能なメモリを作り、テキストを入れる。
2. 1で作成した参照アドレスをヒープに保存し、それをString
に保存する。 -
"Ferris"
、"Sarah"
は関数呼び出し中にメモリ上の位置が変更されないため、スタックメモリに入る。
タプルライクな構造体
Rustではタプルのような構造体を使用できる。
struct Location(i32, i32);
fn main() {
// これもスタックに入れられる構造体です。
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}
ユニットライクな構造体
Rustであフィールドを持たない構造体を宣言できる。
ユニット(Unit)は()
の別称で、こういった構造体がユニットライクと呼ばれる。
ユニットライクな構造体はあまり使われない。
struct Marker;
fn main() {
let _m = Marker;
}
列挙型
列挙型では、enum
キーワードにより新しい型を生成することができる。
この型では複数のタグ付けされた値を保持することができる。
列挙型とmatch
文は相性が良く、すべての列挙値に対する処理を簡単に記述することができ、コードの品質を維持することにも役立つ。
#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます。
enum Species {
Crab,
Octopus,
Fish,
Clam
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
let ferris = SeaCreature {
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
match ferris.species {
Species::Crab => println!("{} is a crab",ferris.name),
Species::Octopus => println!("{} is a octopus",ferris.name),
Species::Fish => println!("{} is a fish",ferris.name),
Species::Clam => println!("{} is a clam",ferris.name),
}
}
値を持つ列挙型
enum
では1つ以上のデータを持つことができ、C言語のunion
のような表現ができる。
match
文を用いて列挙値に対するパターンマッチングを行う際、各データを変数名に紐付けることができる。
#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます。
enum Species { Crab, Octopus, Fish, Clam }
enum PoisonType { Acidic, Painful, Lethal }
enum Size { Big, Small }
enum Weapon {
Claw(i32, Size),
Poison(PoisonType),
None
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}
fn main() {
// SeaCreatureのデータはスタックに入ります。
let ferris = SeaCreature {
// String構造体もスタックに入りますが、
// ヒープに入るデータの参照アドレスが一つ入ります。
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small),
};
match ferris.species {
Species::Crab => {
match ferris.weapon {
Weapon::Claw(num_claws,size) => {
let size_description = match size {
Size::Big => "big",
Size::Small => "small"
};
println!("ferris is a crab with {} {} claws", num_claws, size_description)
},
_ => println!("ferris is a crab with some other weapon")
}
},
_ => println!("ferris is some other animal"),
}
}
列挙型のメモリの扱い
- 列挙型のメモリサイズは、自身がもつ最大要素のサイズに等しい。これにより全ての代入可能な値が同じサイズのメモリ空間を利用することを可能にする。
- 要素の型以外に、各要素には数値がついており、どのタグであるかについて示している。
Rustの列挙について
- Rustの列挙はtagged-unionとも言われる。
- 複数の型を組み合わせて新しい型を作ることができ、これがRustがalgebraic typesを持つと言われる理由である。