Skip to content

Instantly share code, notes, and snippets.

@YuukiToriyama
Last active June 26, 2021 00:24
Show Gist options
  • Save YuukiToriyama/52e8144866c001ba82f4ef878806dbef to your computer and use it in GitHub Desktop.
Save YuukiToriyama/52e8144866c001ba82f4ef878806dbef to your computer and use it in GitHub Desktop.
【読書メモ】実践Rustプログラミング入門

Rustの魅力

  • ブラウザエンジンServoの開発を通して生まれた
  • 魅力
    • とにかく実行速度が速い
    • モダンな文法が入っている
    • システム開発からWebまで開発できる
    • 標準で開発ツールがそろっている
    • 「安全性」が担保されている
    • エディションという考え方によって互換性が保たれている

とにかく実行速度が速い

  • C,C++に匹敵する速さをもつ
    • Rustは機械語に直接コンパイルされるから
    • ガベージコレクションをもたない
    • 「ゼロコスト抽象化」を追及している

Rustは機械語に直接コンパイルされる

  • JavaやPythonのようにVMの上で動く言語ではない
    • 仮想マシンを挟むと速度が落ちる
  • Rustは機械語にコンパイルされる
    • もちろん生成物は環境に依存するのでコンパイル時に環境を指定する必要がある

ガベージコレクションを持たない

  • GolangもVMを持たない。それなのにRustがGoより早いのはなぜか
    • GCを持たないから
    • 使っていないメモリ領域を自動的にcleanする機能がない
  • GCは便利だが問題もある
    • メモリ開放がいつ行われるのかわからない
    • メモリ開放を行う際に処理が一時的に停止する
  • Rustはガベージコレクションではなく、「所有権」「借用」「ライフタイム」という概念を持ち込みメモリ問題に対処した

ゼロコスト抽象化

  • もとはC++で生み出された概念
  • 実行速度の低下やメモリ使用量の増加といった犠牲を払わずにプログラムを抽象化できる

モダンな言語機能が一通り入っている

不変・可変を明示的に制御できる

  • 変数割り当ては標準では「不変」
    • 再代入不可
  • ただ、mutキーワードを用いると可変な変数を定義することができる
fn main() {
  let mut num = 5;
  println!("{}", num); // 5
  num = 6;
  println!("{}", num); // 6
}

filter,mapなどを使ってコレクション操作ができる

  • イテレータ
    • 要素をひとつひとつたどっていく操作を抽象化した機構のこと
    • リストやマップなどのコレクションはイテレータをもっている
  • filterやmapなどのアダプターをつかってもコンパイル時に最適化してくれる

代数的データ型とパターンマッチング

  • 代数的データ型
    • OCalm,Haskelなどで用いられている概念
    • enum,構造体,タプル,…
// Option型: 値がないかもしれないことを示す型
pub enum Option<T> {
  None, // 値がない
  Some(T), // 値がある
}
  • パターンマッチング
    • switch文の進化系
    • Rustではさまざまな型をパターンマッチングに通すことができる
fn main() {
  let objective: Option<i32> = Some(1);
  match objective {
    Some(x) if x % 2 == 0 => println!("これは偶数: {}", x),
    Some(x) => println!("これは奇数: {}", x),
    None => println!("値がありません"),
  }
}

強力な型推論

  • コンパイル時に型の情報をコンパイラが補完してくれる
  • 静的型付き言語ではあるが、型注釈を省略できる
fn main() {
  let mut v = vec![]; // v: Vec<?> ?が不明
  v.push(1); // v:Vec<i32> i32と判明
}

トレイト

  • 共通のふるまいを取り出して名前付けしたもの
  • Javaのinterfaceのようなもの

OSからWebアプリまで幅広く対応できる

  • C,C++が扱う領域を扱うことができる
    • OSを書いたり組み込み開発に使うことができる
  • Webアプリのバックエンド開発に使える
    • actix-web
    • 世界最速のWebフレームワークだった

ツール群が充実している

  • Cargo
    • パッケージマネージャ兼ビルドツール
    • ライブラリのインストール(cargo install)、コードのビルド(cargo build)が可能
    • Cargo.tomlが設定ファイル
    • rustの生産性の高さに貢献している
  • エディタサポート
  • 標準フォーマッタ、標準リンター
    • rustfmt,clippy

「安全性」が担保されている

  • メモリ安全でない操作、スレッド安全でない操作を防止する仕組みがある
  • メモリ安全でない操作
    • use-after-free
    • 一度使用して解放したメモリを再参照してしまうこと
    • セキュリティホールの原因となる
  • スレッド安全でない操作
    • データ競合
    • 複数スレッド間で共有する変数に同時書き込みを行った際、データが競合してしまうこと
    • これもセキュリティホールの原因になる
  • メモリ安全、スレッド安全でないコードが発見された場合、コンパイラがエラーを出す

基本的な文法

基本的な型

  • コアライブラリ内で定義されている
    • 数値型
    • 文字列型(str 型)
    • タブル
    • 配列
  • 標準ライブラリ内で定義されている
    • Vec
    • Option
    • Result

数値型

  • アルファベット 1 文字とビット数で表す
  • アルファベット
    • 符号あり整数型: i
    • 符号なし整数型: u
    • 浮動小数点数: f
  • ビット数
    • 8 ビット
    • 16 ビット
    • 32 ビット
    • 64 ビット
    • 128 ビット
  • isize,usize
    • アーキテクチャのメモリ空間に依存して変化
    • 32 ビット環境なら 32 ビット、64 ビット環境なら 64 ビット

文字列型

  • str 型と String 型
  • str 型
    • 文字列スライス
    • メモリ上に存在する文字列データの始点と長さを表している
    • 固定長のため長さを変更することはできない
    • &str
  • String 型
    • 標準ライブラリで定義されている
    • データの変更、長さの変更が可能
    • 文字列操作に向いている
  • str,String とも UTF-8 を用いている
    • 互いに型変換可能
    • String->&str
      • String 型の文字列はすでにメモリ上にある
      • そのメモリ位置と長さをコピーするだけであるからメモリを圧迫しない
    • &str->String
      • あらたにメモリが確保される
fn main() {
	let str1: String = String::from("Hello,world"); // String型の文字列を定義
	let str2: &str = &str1; // String型から&str型への変換
	let str3: String = str2.to_string(); // &str型からString型への変換
}

タプル

  • 異なる型のデータを一緒に収めることができる
  • 関数の返り値として使われることがある
let mut tpl = (2021, "2021");
t.0 = 2022; // .0のような添字で要素を指定する
t.1 = "2020";

配列

  • 特定の型の値を連続的に収めた集合
  • 配列のサイズは固定
    • コンパイル時に確定している必要がある
  • 個別の要素には[]を用いてアクセスする
  • 配列参照時には自動的にスライスとして扱われる
let mut a: [i32; 3] = [0, 1, 2];
let b: [i32; 3] = [0; 3]; // [0, 0, 0]
a[1] = b[1];
a[2] = b[2];
println!("{:?}", &a[1..3]);

ユーザー定義型

  • ユーザーが型を定義できる
    • 構造体 struct
    • 列挙体 enum
struct Animal {
	name: String,
	age: u32
}
let p = Animal {
	name: String::from("ポチ"),
	age: 10
}

enum Event {
	Quit,
	KeyDown(u9),
	MouseDown {
		x: i32,
		y: i32
	}
}
let e1 = Event::Quit;
let w2 = Event::MouseDown {
	x: 10,
	y: 10
}

標準ライブラリでみられる型

  • Option,Result,Vec,Box

Option

  • 列挙型
  • データが存在する場合と存在しない場合を表現できる
    • None: データが存在しない
    • Some(T): 型 T のデータが存在する
pub enum Option<T> {
	None,
	Some(T),
}

Result

  • 列挙型
  • 処理の結果が成功する場合とエラーになる場合を表現できる
    • Ok(T): 成功
    • Err(E): 失敗
  • 例: Result<i32, String>
    • 成功時は整数(i32)を返す
    • 失敗時は文字列(String)のエラーメッセージを返す
  • 処理の結果として Result 型を返す関数は多い
    • 結果を Result として受け取った場合、match や if let を用いて処理する
pub enum Result(<T, E>) {
	Ok(T),
	Err(E),
}
fn hoge(num: i32) -> Result<i32, String> {
    if num > 10 {
        Ok(num)
    } else {
        Err("error".to_string())
    }
}

fn main() {
    let result = hoge(10);
    match result {
        Ok(number) => println!("{}", number),
        Err(error) => println!("{}", error),
    }
}
  • unwrap_or()を用いることもできる
    • Ok()だった場合はそのまま展開
    • Err()だった場合は引数に与えた値を返す
let mut result: Result<i32, String> = Ok(200);
result.unwrap_or(-1) // 200
result = Err("error occurs".to_string());
result.unwrap_or(-1) // -1
  • ?演算子
    • Ok だった場合は値を展開し、
    • Err だった場合はその Err()をそのまま return する

Vec

  • ベクタ型
  • 配列と違う点
    • 長さを変えられる
  • vec![]マクロで初期化できる
  • push()
    • 最後尾に要素を追加
  • pop()
    • 最後尾の要素を削除
  • 要素数以上の範囲にアクセスしようとすると panic が起きて強制終了
    • get()を用いると panic は起きない
    • None を返す
let mut vec1 = vec![1,2,3,4,5]; // [1,2,3,4,5]
let vec2 = vec![0; 5]; // [0,0,0,0,0]

vec1.push(6); // [1,2,3,4,5,6]

Box

  • スタック領域ではなくヒープ領域にデータを保管する
  • コンパイル時にデータのサイズが確定していなくても大丈夫
    • 確保したいタイミングで必要な分を確保
    • 不要になったタイミングでメモリを開放
  • Box 型を用いると、ヒープ領域にデータを保存し、スタック領域にヒープ領域へのポインタを置く
  • 使用例
    • コンパイル時にサイズがわからない型を格納するとき
    • 大きなサイズの型の値を渡すが、データの中身をコピーせず、ポインタで渡す時
    • 共通のトレイトを実装した様々な型を画一的にポインタで扱う時

変数宣言

let

  • 変数束縛のキーワード
  • 変数は不変
    • 可変変数にしたい場合は mut キーワードを用いる

制御構文

if

  • 条件
    • 評価した値を変数に束縛したり関数の引数にすることができる
fn main() {
    let num = -10;
    let result = if num > 0 {
        num
    } else {
        num * (-1)
    };
    println!("{:?}", result);
}

ループ

loop

  • コードを何度も繰り返し実行
  • break を使ってループから抜け出す
  • loop は式
fn main() {
    let mut count = 0;
    let result = loop {
        println!("{}", count);
        count = count + 1;
        if count == 100 {
            break count;
        }
    };
    println!("{:?}", result);
}

while

  • ある条件のときだけ繰り返し処理を行なう
fn main() {
    let mut count = 0;
    while count < 10 {
        println!("{}", count);
        count = count + 1;
    }
}

for

fn main() {
    let array = [0,1,2,3,4,5,6,7,8,9];
    for elem in &array {
        println!("{}", elem);
    }
}

match

  • switch 文のように変数の値によって処理を分岐させられる
    • パターンに合致するかを判別し処理を実行する
  • match は式である
    • 変数に束縛することも可能
fn main() {
    let num: i32 = 10;
    match num {
        0 => println!("zero"),
        10 => println!("ten"),
        _ => println!("neither zero nor ten") // アンダースコアはそれ以外
    }
}

Range

  • 特定の範囲の数値を指定する場合に用いる
for num in 1..5 {
	println!(num);
}

impl

struct Animal {
    name: String,
    nation: String,
}
impl Animal {
    fn say_name(&self) {
        println!("I am {}.", self.name);
    }
    fn from_where(&self) {
        println!("I am from {}.", self.nation);
    }
}

fn main() {
    let animal = Animal{
        name: String::from("Panda"),
        nation: String::from("China"),
    };
    animal.say_name();
    animal.from_where();
}

Rust を支える言語機能

ゼロコスト抽象化

  • プログラムを安全で高速に処理するための機能
  • カプセル化
    • 密接な関係にあるデータと処理をまとめ、あえてオブジェクトの外から見えなくすること
  • ポリモーフィズム
    • 使われ方が同じオブジェクトが複数あったばあい、外から見たインターフェースを決め使われ方を共通化する
  • 抽象化にかかるコスト
    • カプセル化、ポリモーフィズムを使うと抽象化ができる
    • しかし抽象的なオブジェクトから具体的な処理を導く際にメモリ消費などのコストがかかる
  • ゼロコスト抽象化とは
    • 抽象化した処理を実行するために必要な負荷を可能な限り小さくすること

trait と dyn

  • trait とは
    • 様々な型に共通のメソッドを実装できる仕組み
trait Tweet {
	fn tweet(&self);
	fn tweet_twice(&self) {
		self.tweet();
		self.tweet();
	}
	fn shout(&self) {
		println!("piyopiyo");
	}
}

struct Duck;
impl Tweet for Duck {
	// tweet()を定義するだけでtweet_twice(), shout()が付いてくる
	fn tweet(&self) {
		println!("Quack!");
	}
}
fn main() {
	let duck = Duck {};
	duck.tweet();
	duck.tweet_twice();
	duck.shout();
}

マーカトレイト

  • メソッドは持っていない
  • 意味や役割を与えるためのトレイト
    • Copy
    • Send
    • Sized
    • Sync

ジェネリクス

  • ある特定の型に対して定義した処理を別の型にも用意することが出来る機能
  • 任意の型 T,S を引数に取りタプルにまとめて返す
fn to_tuple<T,S>(t:T, s:S) -> (T,S) {
	(t,s)
}
fn main() {
	let t1 = to_tuple(10, 20); // (i32,i32)
	let t2 = to_tuple("Hello", "world"); // (String, String)
	let t3 = to_tuple(3, "years old"); // (i32, String)
}

所有権と借用

  • 所有権、借用、ライフタイム
  • 所有権
    • それぞれの値には所有権がある
    • 所有権を持っているのはただ一つの変数
    • その変数がスコープから外れたら紐付けられたその値は破棄される
      • 所有者が一人であるからメモリの二重解放問題が起きない
  • 借用
    • 値の所有権が一人に限られると困ることもある
      • 関数の引数に値を渡す際に所有権が移ってしまう
    • 値自身ではなく値の参照を貸し出す機能
    • 所有権はもとの所有者がもったままでその値を参照する権利だけをもらう
  • ライフタイム
    • 値の参照を借用している間に値が破棄されてしまっては困る
    • 参照は所有者より長生きではいけない
    • 参照には安全に利用できる期間を明確にする必要がある

ムーブセマンティクス

  • =で値の所有権が移る
  • C++の場合
    • =では値がコピーされる
  • Rust では
    • 右の変数が所有していた値の所有権が左の変数に渡る
    • Copy トレイトを持つ型はコピーセマンティクスになり=で値がコピーされる
    • ほとんどの型ではムーブセマンティクスがデフォルト
  • ムーブセマンティクスによって所有権システムが機能する
struct Color {
  r: i32,
  g: i32,
  b: i32,
}
fn main() {
  let a = Color{
    r: 255,
    g: 255,
    b: 255
  };
  let b = a; // 所有権が譲渡される
  println!("{}, {}, {}", b.r, b.g, b.b);
}

借用

  • 値の参照を渡す方法を借用という
    • 不変な参照を渡す場合と可変な参照を渡す場合でルールが異なる
  • 不変な参照を渡す場合
    • いくつでも参照を渡すことが出来る
    • let x = 5; let y = &x; let z = &x;
  • 可変な参照を渡す場合
    • 一度に渡せる参照はひとつだけ
    • 値の更新が発生した場合に値が壊れる可能性があるため
    • let mut = 5; let y = &mut x; let z = &mut x; //🙅
  • 不変な参照渡し
    • &x
  • 可変な参照渡し
    • &mut x
fn print_data(data: &String) {
	println!("{}", data);
}

fn main() {
	let data = "Hello, world".to_string();
	print_data(&data); // 関数print_data()にはdataの参照しか渡していないから所有権は変わらない
	println!("{}", data);
}

RAIL

  • Resource Acquisition Is Initialization
    • リソースの取得は初期化である
    • 変数の初期化時にリソースの確保を行ない、
    • 変数を破棄する時にリソースの解放を行なう
  • メモリに限らず、開いているファイル、データベース接続なども変数破棄時に開放される
  • Dropトレイト
    • デストラクタ
    • 何らかの解放処理が必要な場合に便利
    • 保有しているリソースを開放してくれる
struct Droppable;
impl Drop for Droppable {
	fn drop(&mut self) {
		println!("リソースを開放します");
	}
}
fn main() {
	{
		let d = Droppable;
	} // 変数dのライフタイムが終わる
}

スレッド安全性

  • マルチスレッドプログラミングを行なう上で重要

スレッド間の情報共有

  • 共有メモリ
    • 複数のスレッドで同じメモリ領域を共有する
    • std::sync::{Arc, Mutex}を用いる
    • std::sync::Arc
      • Atomatically Reference Counted
      • 所有権を共有するため所有権を所有している所有者の数をカウントするユーティリティ
      • 所有者が0になったところでメモリを解放
    • std::sync::Mutex
      • lockメソッドにより排他制御を行なうためのユーティリティ
      • あるスレッドがlockを使用するとそれ以外のスレッドのlockは完了しない(ストップする)
      • そのスレッドがアクセスを完了し参照を破棄→他のスレッドに出番が回る
  • メッセージパッシング
    • 各スレッドがお互いにメッセージをやり取りしながら動作するしくみ

テスト

  • ソースコードとテストコードを同じファイルの中に記述できる
  • テスト関数にはアトリビュート#[test]を用いる
  • cargo testコマンドでテスト実行

assertマクロ

  • assert!()
    • 第一引数がfalseのときパニックを起こす
  • assert_eq!()
    • 2つの引数の値を比較して値が等しいことを確認する
  • assert_ne!()
    • 2つの値が異なることを期待するマクロ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment