はじめに
rustlings などで Rust を勉強した時の記録。
自分だけ分かればいいかという気持ちで書いているので参考になるかわからないが残しておく。
基本
配列
配列のデータ型は [T;N] T は型、N はコンパイル時に決まる固定長
[X] で取得できる
{:?} で全て表示できる。
fn main() {
let nums: [i32; 3] = [1, 2, 3];
println!("{:?}", nums);
println!("{}", nums[1]);
}
以下のようにして文字列の配列を作成することもできる。
fn main() {
// TODO: Create an array called `a` with at least 100 elements in it.
let a = ["this is rustlings test"; 100];
if a.len() >= 100 {
println!("Wow, that's a big array!");
} else {
println!("Meh, I eat arrays like that for breakfast.");
panic!("Array not big enough, more elements needed");
}
}
タプル
タプルは x.0 のような形で取得する。
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}
fn main() {
// 戻り値をタプルで返す
let result = swap(123, 321);
println!("{} {}", result.0, result.1);
// タプルを2つの変数に分解
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b);
}
unit と呼ばれる空のタプル () もある
HashMap
python の辞書型みたいなやつ
https://doc.rust-lang.org/book/ch08-03-hash-maps.html
fn fruit_basket() -> HashMap<String, u32> {
// TODO: Declare the hash map.
let mut basket = HashMap::new();
// Two bananas are already given for you :)
basket.insert(String::from("banana"), 2);
// TODO: Put more fruits in your basket.
basket.insert(String::from("apple"), 2);
basket.insert(String::from("mango"), 2);
basket
}
制御フロー
if
C 言語に似ている。演算子は同じ
fn main() {
let x = 42;
if x < 42 {
println!("42 より小さい");
} else if x == 42 {
println!("42 に等しい");
} else {
println!("42 より大きい");
}
}
loop / while
C 言語の while True は loop
fn main() {
let mut x = 0;
loop {
x += 1;
if x == 42 {
break;
}
}
println!("{}", x);
}
普通の While もある
loop では break 時に値を返すことができる
fn main() {
let mut x = 0;
let v = loop {
x += 1;
if x == 13 {
break "13 を発見";
}
};
println!("loop の戻り値: {}", v);
}
for
for は bash と python に近い
.. は python の range のように終了番号の手前まで
..= は終了番号も含む
fn main() {
for x in 0..5 {
println!("{}", x);
}
for x in 0..=5 {
println!("{}", x);
}
}
match
switch ではなく match
全てのケースを用意しなくてはならない
_ がどのパターンにもマッチしなかった時
マッチした数字を変数に格納することもできる
fn main() {
let x = 42;
match x {
0 => {
println!("found zero");
}
// 複数の値にマッチ
1 | 2 => {
println!("found 1 or 2!");
}
// 範囲にマッチ
3..=9 => {
println!("found a number 3 to 9 inclusively");
}
// マッチした数字を変数に束縛
matched_num @ 10..=100 => {
println!("found {} number between 10 to 100!", matched_num);
}
// どのパターンにもマッチしない場合のデフォルトマッチが必須
_ => {
println!("found something else!");
}
}
}
値を返す
if やmatch, 関数, ブロックの最後に ; がないのは戻り値として扱われる。下のコードだと v+4 が該当する。
ブロックのスコープは分離されている。
fn example() -> i32 {
let x = 42;
// Rust の三項式
let v = if x < 42 { -1 } else { 1 };
println!("if より: {}", v);
let food = "ハンバーガー";
let result = match food {
"ホットドッグ" => "ホットドッグです",
// 単一の式で値を返す場合、中括弧は省略可能
_ => "ホットドッグではありません",
};
println!("食品の識別: {}", result);
let v = {
// ブロックのスコープは関数のスコープから分離されている
let a = 1;
let b = 2;
a + b
};
println!("ブロックより: {}", v);
// Rust で関数の最後から値を返す慣用的な方法
v + 4
}
fn main() {
println!("関数より: {}", example());
}
データ構造
struct
C とかにある strust と基本は同じ
struct SeaCreature {
// String は構造体である。
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
構造体をインスタンス化するときは、フィールドデータをメモリ上で隣り合うように配置する。
フィールドの値は . 演算子で取り出すことができる。
文字列 “Ferris” はデータメモリへ、構造体 String はフィールド (animal_type, name, weapon) と隣合うかたちでスタックに保存される。
ferris, sarah はスタックへ。
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
);
}
タプルのような構造体もある
struct Location(i32, i32);
fn main() {
// これもスタックに入れられる構造体です。
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}
空のユニットもある。
struct Marker;
fn main() {
let _m = Marker;
}
メソッド
特定のデータ型に紐づく関数
スタティックメソッド
特定の型そのものに紐づく関数
:: で呼び出す
インスタンスメソッド
特定の型のインスタンスに紐づく関数
. で呼び出す
fn main() {
// スタティックメソッドでStringインスタンスを作成する。
let s = String::from("Hello world!");
// インスタンスを使ってメソッド呼び出す。
println!("{} is {} characters long.", s, s.len());
}
メモリ
データメモリ
スタティックなデータ。読み取り専用。
コンパイラはこういうデータをメモリ上の既定の場所に保存する。
スタックメモリ
関数内の変数を保存する領域。スタック領域。
ヒープメモリ
動的なデータ
列挙型
enum で新しい型を作ることができる。この型はタグ付された値を持つことができる。
#![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 は複数の型を持つことができる。
複数の方を組み合わせて新しい型を作ることができるので algebraic types と言われる。
#![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"),
}
}
ジェネリック型
struct, enum で使用し、コンパイル時に型を決定するもの。つまり、インスタンスを生成するコードから型を推論する。
明示的にジェネリック型と指定するには ::
// 部分的に定義された構造体型
struct BagOfHolding<T> {
item: T,
}
fn main() {
// 注意: ジェネリック型を使用すると、型はコンパイル時に作成される。
// ::<> (turbofish) で明示的に型を指定
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };
// ジェネリック型でも型推論可能
let float_bag = BagOfHolding { item: 3.14 };
// 注意: 実生活では手提げ袋を手提げ袋に入れないように
let bag_in_bag = BagOfHolding {
item: BagOfHolding { item: "boom!" },
};
println!(
"{} {} {} {}",
i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
);
}
Option
enum Option<T> {
None,
Some(T),
}
result
enum Result<T, E> {
Ok(T),
Err(E),
}
これを使うための演算子 ? がある
do_something_that_might_fail()?
match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() -> Result<(), String> {
// コードが簡潔なのに注目!
let v = do_something_that_might_fail(42)?;
println!("発見 {}", v);
Ok(())
}
unwrap
Option/Result 内の値を取得し、None/Err の場合は panic! を発生させる
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() -> Result<(), String> {
// 簡潔ですが、値が存在することを仮定しており、
// すぐにダメになる可能性があります。
let v = do_something_that_might_fail(42).unwrap();
println!("発見 {}", v);
// パニックするでしょう!
let v = do_something_that_might_fail(1).unwrap();
println!("発見 {}", v);
Ok(())
}
ベクタ型
可変長のリスト
iter() メソッドで for ループにいれれる
fn main() {
// 型を明示的に指定
let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);
// もっと賢く、型を自動的に推論
let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);
// きれいなマクロ!
let string_vec = vec![String::from("Hello"), String::from("World")];
for word in string_vec.iter() {
println!("{}", word);
}
}
ベクタはメモリ上、ヒープに入れられる。
所有権
型のインスタンスを作成して、bind するとメモリリソースが作成される。
bind された変数は所有者と呼ばれる。
struct Foo {
x: i32,
}
fn main() {
// 構造体をインスタンス化し、変数に束縛してメモリリソースを作成
let foo = Foo { x: 42 };
// foo は所有者
}
関数の返り値から所有権を渡すこともできる
struct Foo {
x: i32,
}
fn do_something() -> Foo {
Foo { x: 42 }
// 所有権は外に移動
}
fn main() {
let foo = do_something();
// foo は所有者になる
// 関数のスコープの終端により、foo はドロップ
}
drop
スコープの終わりにリソースのデストラクトと開放が行われる。
これを drop という
Rust ではガベージコレクションがない
struct Foo {
x: i32,
}
fn main() {
let foo_a = Foo { x: 42 };
let foo_b = Foo { x: 13 };
println!("{}", foo_a.x);
println!("{}", foo_b.x);
// foo_b はここでドロップ
// foo_a はここでドロップ
}
所有権の移動
所有者が引数として渡されると関数の仮引数に所有権が移動する。
移動後は元の関数ではその変数が使えなくなる。
struct Foo {
x: i32,
}
fn do_something(f: Foo) {
println!("{}", f.x);
// f はここでドロップ
}
fn main() {
let foo = Foo { x: 42 };
// foo の所有権は do_something に移動
do_something(foo);
// foo は使えなくなる
}
借用
& 演算子を使ってリソースへのアクセスを借用できる。
シンボリックリンクのようなイメージ(?)
struct Foo {
x: i32,
}
fn main() {
let foo = Foo { x: 42 };
let f = &foo;
println!("{}", f.x);
// f はここでドロップ
// foo はここでドロップ
}
借用には以下のルールがある。
- Rust では、可変な参照が 1 つだけか、不変な参照が複数かのどちらかが許可されます。両方を同時には使用できません。
これはデータの競合を防ぐため
- 参照は所有者よりも長く存在してはなりません。
存在しないデータの参照を防ぐため (C でいうダングリングポインタ)
struct Foo {
x: i32,
}
fn do_something(f: &mut Foo) {
f.x += 1;
// f への可変な参照はここでドロップ
}
fn main() {
let mut foo = Foo { x: 42 };
do_something(&mut foo);
// 関数 do_something で可変な参照はドロップされるため、
// 別の参照を作ることが可能
do_something(&mut foo);
// foo はここでドロップ
}
可変な借用
&mut で変更可能なアクセスを借用できる
可変な借用されている間は所有者は移動や変更ができない
つまり Rust では 2 つの変数から値を変更できない
struct Foo {
x: i32,
}
fn do_something(f: Foo) {
println!("{}", f.x);
// f はここでドロップ
}
fn main() {
let mut foo = Foo { x: 42 };
let f = &mut foo;
// 失敗: do_something(foo) はここでエラー
// foo は可変に借用されており移動できないため
// 失敗: foo.x = 13; はここでエラー
// foo は可変に借用されている間は変更できないため
f.x = 13;
// f はここから先では使用されないため、ここでドロップ
println!("{}", foo.x);
// 可変な借用はドロップされているため変更可能
foo.x = 7;
// foo の所有権を関数に移動
do_something(foo);
}
参照外し
- 演算子で参照を外すことができる。
C のポインタに近い(?)
- で所有者の値をコピーすることもできる
fn main() {
let mut foo = 42;
let f = &mut foo;
let bar = *f; // 所有者の値を取得
*f = 13; // 参照の所有者の値を設定
println!("{}", bar);
println!("{}", foo);
}
参照の参照
参照の一部を参照することができる。
struct Foo {
x: i32,
}
fn do_something(a: &Foo) -> &i32 {
return &a.x;
}
fn main() {
let mut foo = Foo { x: 42 };
let x = &mut foo.x;
*x = 13;
// x はここでドロップされるため、不変な参照が作成可能
let y = do_something(&foo);
println!("{}", y);
// y はここでドロップ
// foo はここでドロップ
}
ref
所有権の移動ではなく借用を明示する。 パターンマッチングにおいて所有権が移動してしまうのを防げる。
Before:
let maybe_name = Some(String::from("Alice"));
// The variable 'maybe_name' is consumed here ...
match maybe_name {
Some(n) => println!("Hello, {n}"),
_ => println!("Hello, world"),
}
// ... and is now unavailable.
println!("Hello again, {}", maybe_name.unwrap_or("world".into()));
After:
let maybe_name = Some(String::from("Alice"));
// Using `ref`, the value is borrowed, not moved ...
match maybe_name {
Some(ref n) => println!("Hello, {n}"),
_ => println!("Hello, world"),
}
// ... so it's available here!
println!("Hello again, {}", maybe_name.unwrap_or("world".into()));
明示的なライフタイム
変数のライフタイムを共有していることを示すために ’ 演算子を使う
関数はどの引数と戻り値とがライフタイムを共有しているかを指定できる
わかりやすく説明すると、引数の参照先のデータは返り値よりも早く drop してはいけないと指定している。
なお、これは”参照のみ”適用される。つまり参照先のデータが有効であることを保証する。
struct Foo {
x: i32,
}
// 引数 foo と戻り値はライフタイムを共有
fn do_something<'a>(foo: &'a Foo) -> &'a i32 {
return &foo.x;
}
fn main() {
let mut foo = Foo { x: 42 };
let x = &mut foo.x;
*x = 13;
// x はここでドロップされるため、不変な参照が作成可能
let y = do_something(&foo);
println!("{}", y);
// y はここでドロップ
// foo はここでドロップ
}
引数や戻り値のライフタイムをコンパイラが解決できない場合に使われる
戻り値を元の関数でも引き続き使いたいことを指定するっぽい
struct Foo {
x: i32,
}
// foo_b と戻り値はライフタイムを共有
// foo_a のライフタイムは別
fn do_something<'a, 'b>(foo_a: &'a Foo, foo_b: &'b Foo) -> &'b i32 {
println!("{}", foo_a.x);
println!("{}", foo_b.x);
return &foo_b.x;
}
fn main() {
let foo_a = Foo { x: 42 };
let foo_b = Foo { x: 12 };
let x = do_something(&foo_a, &foo_b);
// ここから先は foo_b のライフタイムしか存在しないため、
// foo_a はここでドロップ
println!("{}", x);
// x はここでドロップ
// foo_b はここでドロップ
}
もし以下のような関数を書いたら以下のようなエラーがでる
fn do_something(foo_a: &Foo, foo_b: &Foo) -> &i32 {
println!("{}", foo_a.x);
println!("{}", foo_b.x);
return &foo_b.x;
}
help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `foo_a` or `foo_b`
関数だけでなく、データ型にもライフタイムを指定できる。
以下では i が借用された i32 型が入ることが指定されており、そのライフタイムが Foo と共有することを指定している。
struct Foo<'a> {
i:&'a i32
}
fn main() {
let x = 42;
let foo = Foo {
i: &x
};
println!("{}",foo.i);
}
スタティックライフタイム
プログラムの終了まで持続するメモリリソースで、ドロップすることがない
‘static で指定する。
スタティックライフタイムを持つリソースを参照する場合、それも ‘static にしなければいけない。でないと参照がドロップしてしまう。
static変数は型を明示的に指定しないといけない
static PI: f64 = 3.1415;
fn main() {
// スタティック変数は関数スコープでも定義可能
static mut SECRET: &'static str = "swordfish";
// 文字列リテラルは 'static ライフタイム
let msg: &'static str = "Hello World!";
let p: &'static f64 = &PI;
println!("{} {}", msg, p);
// ルールを破ることはできますが、それを明示する必要があります。
unsafe {
// 文字列リテラルは 'static なので SECRET に代入可能
SECRET = "abracadabra";
println!("{}", SECRET);
}
}
テストの書き方
https://doc.rust-lang.org/book/ch11-01-writing-tests.hello_html
#[test] という属性を関数の前に書く。
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
そして $ cargo test コマンドでテストできる。
逆にパニックが期待される時は #[should_panic] をつける
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {value}.");
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
リテラル
文字列リテラル
文字列リテラルの型は &‘static str
& はメモリの場所を指しているという意味。C の char * のような感じ
‘static はスタティックライフタイム
str は utf-8 のバイト列であることを意味する
fn main() {
let a: &'static str = "こんにちは 🦀";
println!("{} {}", a, a.len());
}
エスケープ
他の言語と同じ
- \n - 改行
- \r - キャリッジ・リターン
- \t - タブ
- \ - バックスラッシュ
- \0 - null
- ’ - シングルクォート
複数行
デフォルトで複数行に対応している。
改行したくない場合は、\ を入れる。
fn main() {
let haiku: &'static str = "
書いてみたり
けしたり果ては
けしの花
- 立花北枝";
println!("{}", haiku);
println!("こんにちは \
世界") // 世界の前にある間隔は無視されます
}
生文字リテラル
r#” で始まり、”# で終わる文字列
fn main() {
let a: &'static str = r#"
<div class="advice">
生文字列は様々な場面で役に立ちます。
</div>
"#;
println!("{}", a);
}
ファイルから文字列リテラルを読み込む
include_str! マクロが使える。
let hello_html = include_str!("hello.html");
スライス
&str の基本的なメソッドとしては以下がある
- len() で長さ
- starts_with, ends_with
- is_empty
- find (最初の位置を Option
で返す)
fn main() {
let a = "hi 🦀";
println!("{}", a.len());
let first_word = &a[0..2];
let second_word = &a[3..7];
// let half_crab = &a[3..5]; は失敗します。
// Rust は無効な unicode 文字のスライスを受け付けません。
println!("{} {}", first_word, second_word);
}
chars
utf-8 を扱うために、char 型がある
char を使うことで複数バイトで一文字を表すものでも簡単に扱える
fn main() {
// 文字をcharのベクトルとして集める
let chars = "hi 🦀".chars().collect::<Vec<char>>();
println!("{}", chars.len()); // should be 4
// chars は 4 バイトなので、u32 に変換することができる
println!("{}", chars[3] as u32);
}
String
ヒープ領域に utf-8 を持つ構造体
基本的なメソッド
- push_str 文字列の最後に utf-8 文字列を追加
- replace 置換する
- to_lowercase, to_uppercase
- trim 空白を切り取る
fn main() {
let mut helloworld = String::from("hello 🦀");
helloworld.push_str(" world");
helloworld = helloworld + "!";
println!("{}", helloworld);
}
関数パラメータとしてのテキスト
一般的に文字列スライスとして関数に渡される。そのためほとんど所有権を渡す必要がない
fn say_it_loud(msg:&str){
println!("{}!!!",msg.to_string().to_uppercase());
}
fn main() {
// say_it_loudは&'static strを&strとして借用することができます
say_it_loud("hello");
// say_it_loudはStringを&strとして借用することもできます
say_it_loud(&String::from("goodbye"));
}
文字列の構築
concat, join が使える
fn main() {
let helloworld = ["hello", " ", "world", "!"].concat();
let abc = ["a", "b", "c"].join(",");
println!("{}", helloworld);
println!("{}",abc);
}
format! マクロもある。python とほぼ同じ
fn main() {
let a = 42;
let f = format!("secret to life: {}",a);
println!("{}",f);
}
変換
to_string で文字列に変換できる。
parse を使うことで、文字列を特定の型に変換できる。失敗する可能性があるので Result 型で返される。
fn main() -> Result<(), std::num::ParseIntError> {
let a = 42;
let a_string = a.to_string();
let b = a_string.parse::<i32>()?;
println!("{} {}", a, b);
Ok(())
}
オブジェクト志向プログラミング (OOP)
OOP とは
主に以下の特徴を持つプログラミング言語のこと
- カプセル化 - データと関数を オブジェクト という一つの型の概念的な単位で関連付けること。
- 抽象化 - データや関数のメンバを隠して、オブジェクトの実装の詳細を難読化すること。
- ポリモーフィズム - 1つのインターフェースを通して、異なるタイプのオブジェクトと相互作用する能力のこと。
- 継承 - 他のオブジェクトからデータや振る舞いを引き継ぐこと。
ただ Rust は OOP ではない
Rust では継承機能を持っておらず、親構造体からフィールド、関数を継承することができない
カプセル化
いくつかの関数を呼び出すことができるオブジェクトを作ることができる。
これらの関数はメソッドとも呼ばれる
どのメソッドも最初のパラメータは、メソッドを呼び出したインスタンスの参照でなければならない
- &self インスタンスへの不変の参照
- &mut self インスタンスへの可変の参照
メソッドは impl 内で定義する
struct SeaCreature {
noise: String,
}
impl SeaCreature {
fn get_sound(&self) -> &str {
&self.noise
}
}
fn main() {
let creature = SeaCreature {
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}
デフォルトだとフィールド、メソッドはそのモジュールでしか使えない。
pub をつけることで外部に公開できる
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}
トレイト
トレイトは構造体型にメソッド群を関連付けることができる。
トレイト内のメソッドは実装ブロックの中で定義される。
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
fn make_alot_of_noise(&self){
self.make_noise();
self.make_noise();
self.make_noise();
}
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}
トレイト内の関数は構造体の内部フィールドに直接アクセスすることはできませんが、多くのトレイトの実装の間で動作を共有するのに便利です。
トレイトの参考記事: https://qiita.com/ishishow/items/23cd4dd8291145f2db71
パラメーターとしてのトレイト
関数の引数としてトレイトを実装した構造体を表すことができる。
例えば以下は Summary というトレイトを実装した構造体を引数に指定している。
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
なお、以下のようにトレイトは複数指定できる。
pub fn notify(item: &(impl Summary + Display)) {
トレイトの継承
トレイトは他のトレイトから継承することができる。
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
trait LoudNoiseMaker: NoiseMaker {
fn make_alot_of_noise(&self) {
self.make_noise();
self.make_noise();
self.make_noise();
}
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
impl LoudNoiseMaker for SeaCreature {}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}
静的ディスパッチ、動的ディスパッチ
インスタンスの型が分かっているとき、その型を指定して実行するのが静的ディスパッチ
型がわからないとき、&dyn MyTrait のように指定して実行するのが動的ディスパッチ。実際の関数を見つける必要があるのでわずかに遅くなる
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn static_make_noise(creature: &SeaCreature) {
// we know the real type
creature.make_noise();
}
fn dynamic_make_noise(noise_maker: &dyn NoiseMaker) {
// we don't know the real type
noise_maker.make_noise();
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
static_make_noise(&creature);
dynamic_make_noise(&creature);
}
&dyn MyTrait のような型をトレイトオブジェクトと呼ばれ、トレイトオブジェクトは、インスタンスのポインタと、インスタンスのメソッドへの関数ポインタのリストを持っている。
ジェネリック
汎用的な型を指定することができる
例えば以下のように型 T を使うことで、数字も文字も引数にできる (PartialOrd な型に限る)
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {result}");
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {result}");
}
これはメソッドの定義にも使える。
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
引数に実装しなければならないトレイトを指定することで型を制限できる。
例えば以下は foo は型 T であり、型 T は Foo というトレイトを実装していなければならない。
fn my_function<T>(foo: T)
where
T:Foo
{
...
}
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn generic_make_noise<T>(creature: &T)
where
T: NoiseMaker,
{
// we know the real type at compile-time
creature.make_noise();
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
generic_make_noise(&creature);
}
ジェネリクスは以下のようにも記述できる。
fn my_function(foo: impl Foo) {
...
}
Box
データをスタックからヒープに移動させるためのデータ構造
実態は、スマートポインタと呼ばれる構造体でヒープへのポインタをもつ
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
struct Ocean {
animals: Vec<Box<dyn NoiseMaker>>,
}
fn main() {
let ferris = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
let sarah = SeaCreature {
name: String::from("Sarah"),
noise: String::from("swish"),
};
let ocean = Ocean {
animals: vec![Box::new(ferris), Box::new(sarah)],
};
for a in ocean.animals.iter() {
a.make_noise();
}
}
box は主に以下の用途で使う。
- コンパイル時に型のサイズが決まらなく、その型の値を決まったサイズとして使用する時
- 大きなデータの所有権を移動したいが、データ自体のコピーはしたくない時
- ある値を所有したいが、それが特定の型ではなく、特定のトレイとを実装する型であることだけを気にする場合
1 つめは以下のような場合。
Cons の2番目の引数は再帰的になっており、サイズがコンパイル時に決まらない。 Box はヒープのポインタを保持するのでサイズは一定になる。値を直接保存する代わりに、値へのポインタを格納して間接的に値を格納するようにデータ構造を変更している。
これを間接参照と行ったりするらしい
#[derive(PartialEq, Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
// TODO: Create an empty cons list.
fn create_empty_list() -> List {
List::Nil
}
// TODO: Create a non-empty cons list.
fn create_non_empty_list() -> List {
List::Cons(32, Box::new(List::Cons(16, Box::new(List::Nil))))
}
fn main() {
println!("This is an empty cons list: {:?}", create_empty_list());
println!(
"This is a non-empty cons list: {:?}",
create_non_empty_list(),
);
}
スマートポインタ
Raw pointer
参照はraw pointer に変換することができる。
raw pointer には 2 つの種類がある。
*const T : 変化しない型 T へのポインタ *mut T : 変化する可能性のある型 T へのポインタ
このポインタは C のポインタと類似している。
fn main() {
let a = 42;
let memory_location = &a as *const i32 as usize;
println!("Data is here {}", memory_location);
}
dereferencing
参照によって参照されているデータにアクセスしたり操作するのを dereferencing (参照外し) という。
参照外しには * 演算子をつかう。
fn main() {
let a: i32 = 42;
let ref_ref_ref_a: &&&i32 = &&&a;
let ref_a: &i32 = **ref_ref_ref_a;
let b: i32 = *ref_a;
println!("{}", b)
}
参照のフィールドやメソッドにアクセスするには . 演算子を使う。
以下のコードで ref_ref_ref_f の前に *** をつけなくてもいいのは、コンパイラが自動的に dereferencing してくれるから。
なので実質 ***ref_ref_ref_f と同じ。(だからといって明示的につけるとコンパイラエラーが出る)
struct Foo {
value: i32
}
fn main() {
let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);
}
スマートポインタ
別の型にアクセス、参照できる型
通常 Deref, DerefMut, Drop というトレイトを実装する。これらは *, . 演算子を使ったときの動作を定義する。
use std::ops::Deref;
struct TattleTell<T> {
value: T,
}
impl<T> Deref for TattleTell<T> {
type Target = T;
fn deref(&self) -> &T {
println!("{} was used!", std::any::type_name::<T>());
&self.value
}
}
fn main() {
let foo = TattleTell {
value: "secret message",
};
// dereference occurs here immediately
// after foo is auto-referenced for the
// function `len`
println!("{}", foo.len());
}
unsafe
低レベルなメモリの操作に使う。
ポインタの参照外しなどができる。ポインタが指す先になにがあるかわからないので unsafe を宣言してから参照外しを行う。
fn main() {
let a: [u8; 4] = [86, 14, 73, 64];
// this is a raw pointer. Getting the memory address
// of something as a number is totally safe
let pointer_a = &a as *const u8 as usize;
println!("Data memory location: {}", pointer_a);
// Turning our number into a raw pointer to a f32 is
// also safe to do.
let pointer_b = pointer_a as *const f32;
let b = unsafe {
// This is unsafe because we are telling the compiler
// to assume our pointer is a valid f32 and
// dereference it's value into the variable b.
// Rust has no way to verify this assumption is true.
*pointer_b
};
println!("I swear this is a pie! {}", b);
}
Vec
C 言語の malloc は Rust では alloc や Layout
use std::alloc::{alloc, Layout};
use std::ops::Deref;
struct Pie {
secret_recipe: usize,
}
impl Pie {
fn new() -> Self {
// let's ask for 4 bytes
let layout = Layout::from_size_align(4, 1).unwrap();
unsafe {
// allocate and save the memory location as a number
let ptr = alloc(layout) as *mut u8;
// use pointer math and write a few
// u8 values to memory
ptr.write(86);
ptr.add(1).write(14);
ptr.add(2).write(73);
ptr.add(3).write(64);
Pie { secret_recipe: ptr as usize }
}
}
}
impl Deref for Pie {
type Target = f32;
fn deref(&self) -> &f32 {
// interpret secret_recipe pointer as a f32 raw pointer
let pointer = self.secret_recipe as *const f32;
// dereference it into a return value &f32
unsafe { &*pointer }
}
}
fn main() {
let p = Pie::new();
// "make a pie" by dereferencing our
// Pie struct smart pointer
println!("{:?}", *p);
}
ヒープ
Box もスマートポインタ。スタックからヒープにデータを移動させる。
struct Pie;
impl Pie {
fn eat(&self) {
println!("tastes better on the heap!")
}
}
fn main() {
let heap_pie = Box::new(Pie);
heap_pie.eat();
}
std:error:Error
use std::fmt::Display;
use std::error::Error;
struct Pie;
#[derive(Debug)]
struct NotFreshError;
impl Display for NotFreshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "This pie is not fresh!")
}
}
impl Error for NotFreshError {}
impl Pie {
fn eat(&self) -> Result<(), Box<dyn Error>> {
Err(Box::new(NotFreshError))
}
}
fn main() -> Result<(), Box<dyn Error>> {
let heap_pie = Box::new(Pie);
heap_pie.eat()?;
Ok(())
}
Reference Counting (Rc)
Rc というスマートポインタはスタックからヒープにデータを移動し、さらにクローンすることができる。
クローンしたスマートポインタが全てdropしたらヒープが開放される。
use std::rc::Rc;
struct Pie;
impl Pie {
fn eat(&self) {
println!("tastes better on the heap!")
}
}
fn main() {
let heap_pie = Rc::new(Pie);
let heap_pie2 = heap_pie.clone();
let heap_pie3 = heap_pie2.clone();
heap_pie3.eat();
heap_pie2.eat();
heap_pie.eat();
// all reference count smart pointers are dropped now
// the heap data Pie finally deallocates
}
実際は Rc::clone を呼び出すことがほとんど。
Rc::clone の呼び出しはただ参照カウントをインクリメントするだけ。
RefCell
RefCell はスマートポインタをもつデータ構造。
不変または可変参照をとることができる。が、その安全性は実行時に検証されるので失敗するとパニックになる。
use std::cell::RefCell;
struct Pie {
slices: u8
}
impl Pie {
fn eat(&mut self) {
println!("tastes better on the heap!");
self.slices -= 1;
}
}
fn main() {
// RefCell validates memory safety at runtime
// notice: pie_cell is not mut!
let pie_cell = RefCell::new(Pie{slices:8});
{
// but we can borrow mutable references!
let mut mut_ref_pie = pie_cell.borrow_mut();
mut_ref_pie.eat();
mut_ref_pie.eat();
// mut_ref_pie is dropped at end of scope
}
// now we can borrow immutably once our mutable reference drops
let ref_pie = pie_cell.borrow();
println!("{} slices left",ref_pie.slices);
}
Mutex
スマートポインタをもつデータ構造。
データのアクセスを 1 つの CPU に制限し、他の CPU からのアクセスをブロックする。
use std::sync::Mutex;
struct Pie;
impl Pie {
fn eat(&self) {
println!("only I eat the pie right now!");
}
}
fn main() {
let mutex_pie = Mutex::new(Pie);
// let's borrow a locked immutable reference of pie
// we have to unwrap the result of a lock
// because it might fail
let ref_pie = mutex_pie.lock().unwrap();
ref_pie.eat();
// locked reference drops here, and mutex protected value can be used by someone else
}
lock() から返されるのは MutexGuard という型で、LockResult という型をラップしている。
この型は Deref が実装されていて、参照外しをすることでデータにアクセスできる。
また、Drop も実装されており、Drop したらロックをリリースする。
組み合わせ
スマートポインタを組み合わせて使うこともできる。
use std::cell::RefCell;
use std::rc::Rc;
struct Pie {
slices: u8,
}
impl Pie {
fn eat_slice(&mut self, name: &str) {
println!("{} took a slice!", name);
self.slices -= 1;
}
}
struct SeaCreature {
name: String,
pie: Rc<RefCell<Pie>>,
}
impl SeaCreature {
fn eat(&self) {
// use smart pointer to pie for a mutable borrow
let mut p = self.pie.borrow_mut();
// take a bite!
p.eat_slice(&self.name);
}
}
fn main() {
let pie = Rc::new(RefCell::new(Pie { slices: 8 }));
// ferris and sarah are given clones of smart pointer to pie
let ferris = SeaCreature {
name: String::from("ferris"),
pie: pie.clone(),
};
let sarah = SeaCreature {
name: String::from("sarah"),
pie: pie.clone(),
};
ferris.eat();
sarah.eat();
let p = pie.borrow();
println!("{} slices left", p.slices);
}
Project organization
モジュール
Rust のプログラム、ライブラリは crate と呼ばれる
全ての crate はモジュールの階層でできている。そして全ての crate はルートモジュールがある。
プログラムのルートモジュールは main.rs にある。
ライブラリのルートモジュールは lib.rs にある。
他モジュール、crate の参照
use キーを使ってモジュールのアイテムを参照することができる。
std は標準ライブラリ。
use std::f64::consts::PI;
fn main() {
println!("Welcome to the playground!");
println!("I would love a slice of {}!", PI);
}
複数アイテムの参照
1 つのモジュールから複数のアイテムを参照する場合は以下のようにできる。
use std::f64::consts::{PI,TAU}
モジュールの作成
モジュール: foo を作るときは foo.rs ファイルを作成するか、foo ディレクトリに mod.rs ファイルを置く。
モジュールの依存
foo モジュールに依存する場合は以下を書く。
mod foo;
インラインモジュール
モジュールのコードにサブモジュールを直接書ける。
// This macro removes this inline module when Rust
// is not in test mode.
#[cfg(test)]
mod tests {
// Notice that we don't immediately get access to the
// parent module. We must be explicit.
use super::*;
... tests go here ...
}
モジュールの参照
モジュールの参照に便利なキーがある
- crate: ルートモジュール
- super: 親モジュール
- self: 現在のモジュール
export
デフォルトではモジュール内のメンバーは他のモジュールからアクセスできない。
アクセスできるように pub キーを付ける必要がある。
これは構造体でも同様。
prelude
Vec や Box などを use なしで使えているのは、標準ライブラリの prelude モジュールにあるから。
std::prelude::* にあるものは use を使わなくてもどこでも使える。
そして、自分でも prelude を作ることができる。
threads
https://doc.rust-lang.org/book/ch16-01-threads.html
thread::spawn でスレッドを作成できる
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {i} from the spawned thread!");
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {i} from the main thread!");
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
返り値としてハンドラが得られる。
handle.join() でそのスレッドが完了するのを待つことができる。
また、変数の所有権を渡すには move を記載する。
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {v:?}");
});
handle.join().unwrap();
}
channel
チャンネルを作り、スレッド間でデータのやり取りをしたい場合は mpsc::channel() でチャンネルを作れる。(multiple producer, single consumer の略)
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {received}");
}
以下のように transmitter をクローンして複数のソースからデータを送ることができる。
また、rx は for 文でデータを受け取れる。channel がクローズしたらiterationが終わる。
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {received}");
}
Mutex (Threads)
以下のようにロックの所有権をを複数のスレッドに移動することができない。
// This program won't complie!!
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
複数のスレッドで複数のロック所有権を持ちたい場合は Arc を使う。
似たようなのに Rc があるが、これは Send トレイトを実装していなく、スレッドセーフではないので失敗する。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
counter が不変にもかかわらず、内部の値の可変参照を取得できることがわかる。
これはMutex が内部可変性を提供することを示している。
Clippy
lint ツール。以下のコマンドでコードを解析してくれる。
$ cargo clippy
指摘が厳しいので無視したい項目があれば以下のようにする。
#[allow(unused_variables, unused_assignments)]
fn main() {
...