Open48

【メモ】The Rust Programming Language

Haruka MiyataHaruka Miyata

不変がデフォルトで、可変にしたければ mut つける。

let apples = 5; // immutable
let mut bananas = 5; // muttable
Haruka MiyataHaruka Miyata
  • read_line ユーザが標準入力に入力したものを文字列に追加する
    • 上書きではないので可変
  • & 参照であることを伝えてる
    • 受け渡し先が参照であることを伝えたい
std::io::stdin().read_line(&mut guess)

TODO:

  • 引数で型も渡すのなぜ
Haruka MiyataHaruka Miyata

型推論

  • rustが型推論できるから指定不要
let mut guess = String::new();

シャドーイング

// guessはString型
let mut guess = String::new();
// 前の値を覆い隠す
// 型変換のために変数をダブらせないため
let guess: u32 = guess.trim().parse().expect("Please type a number!");

TODO:

  • 型注釈でparseに伝えられるのなぜ?
Haruka MiyataHaruka Miyata

束縛 binding

  • 名前の出現を実体に結び付けること
    • 実行前に分かる束縛:静的束縛
    • 実行してからわかる:動的束縛

参考

Haruka MiyataHaruka Miyata

変数let

  • デフォルト不変
  • グローバルスコープで定義できない
  • mutで可変にできる

定数const

  • 必ず型注釈いる
  • グローバルスコープで定義できる
const MAX_POINTS: u32 = 100_000;
Haruka MiyataHaruka Miyata

上書きではなく、元の値が覆い隠される

let x = 5;
let x = x + 1;
// こっちはだめ
let x = 5;
x = x + 1;

可変のやつに違う型入れるのもだめ

let mut spaces = "   ";
spaces = spaces.len();
Haruka MiyataHaruka Miyata

複数の型が想定される場合は必ず型注釈がいる。

let guess: u32 = "42".parse().expect("Not a number!");
Haruka MiyataHaruka Miyata

タプル

let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;

配列

let a = [1, 2, 3, 4, 5];
let b = [3; 5]; // 3を5要素
let first = a[0];
Haruka MiyataHaruka Miyata
  • 式:なんらかの結果の値によって評価される。文の一部。
    • 5 + 6:値11に評価される式
    • {} ブロック
    • 関数呼び出し
  • 文:なんらかの動作をして値を返さない命令
    • let y = 6;
    • 関数自体
let x = [式が来ることを期待]
// `let y = 6`は文であり値を返さないので、`x`に束縛するものがない
// → エラー
let x = (let y = 6);
Haruka MiyataHaruka Miyata
  • 式は終端に ; を含まない。
  • ;が付くと式になる。
// 値4に評価されるブロック(式)
{
    let x = 3;
    x + 1
}
Haruka MiyataHaruka Miyata

制御

if [bool型を想定した条件式] {}
else {}
  • if [条件式]も式なので、let文の左辺に持って来れる
  • numberは、if式の結果に基づいた値に評価される
let number = if condition { 5 } else { 6 };
  • はじめに出てくる値で型を推測する
|  let number = if condition { 5 } else { "six" };
|                              -          ^^^^^ expected integer, found `&str`
|                              |
|                              expected because of this
Haruka MiyataHaruka Miyata

所有権

  • Rustの各値は、所有者と呼ばれる変数と対応している。
  • いかなる時も所有者は一つである。
  • 所有者がスコープから外れたら、値は破棄される。

メモリ領域

  • スタック:サイズ固定。中身がコンパイル時に確定している。
    • 文字列リテラルなど
  • ヒープ:可変。コンパイル時にサイズが不明。
    • Stringなど
    • 関数を呼んだ時点で、OSにメモリを要求。
    • 終了時に返還する。allocatefreeを良いタイミングで1対1対応させないとバグる。
    • Rustではスコープを抜けたタイミングで返還される。
      • 閉じ括弧のタイミングで、自動でdropを呼んでくれる。
Haruka MiyataHaruka Miyata
  • 単純なコピー
    • 整数は固定サイズが既知
    • 5という値がそのままスタックに積まれる
let x = 5;
let y = x;
  • 「ヒープ領域へのポインタ・長さ・キャパ」の値がスタックに積まれる
let s1 = String::from("hello");
let s2 = s1;
Haruka MiyataHaruka Miyata

二重解放を防ぐ

  • s1s2にコピーされた時点で、コピー元のs1は無効化されて使えなくなる。
  • スコープ抜ける時はs2だけ解放すれば良い。
  • deep copyは不採用。
    • clone使うとできる
Haruka MiyataHaruka Miyata
  • 関数に渡したら無効化される
    • → 関数を抜けるとき、返り値として戻すことで呼び出し元にmoveできる
    • 毎回戻す作業だるい
    • → 参照を使うとgood
Haruka MiyataHaruka Miyata

参照

  • &で渡す
  • 所有権を渡さずに参照先を取得できる
    • 借用:関数の引数に参照を取ること
let s1 = String::from("hello");
let len = calculate_length(&s1);
  • 借用した値は変更不可
let s = String::from("hello");
change(&s);

fn change(some_string: &String) {
    some_string.push_str(", world");
    ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}
  • 変えたいならmutで可変にする
let mut s = String::from("hello");
change(&mut s);
Haruka MiyataHaruka Miyata

同じスコープで可変値は一つしか持てない

  • 可変は認めるが制約をかける
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;

データ競合が起こる状況

  • 2つ以上のポインタが同じデータに同時にアクセスする。
  • 少なくとも一つのポインタがデータに書き込みを行っている。
  • データへのアクセスを同期する機構が使用されていない。

可変値を一つしか許容しないことで、データの競合を防ぐ。

Haruka MiyataHaruka Miyata
  • ダングリングポインタ
    • 無効なメモリ領域を指すポインタ
    • move済みのやつとか
  • Rustにはダングリングを防ぐ仕組みがある
    • ポインタが有効な間に解放しない
    • データへの参照が生きてる間は、データのメモリを解放しない
Haruka MiyataHaruka Miyata

スライス

  • [starting_index..ending_index]
  • スライスの内部構造
    • 開始位置へのポインタ:starting_index
    • 長さ:ending_index - starting_index
let s = "hello world";
let hello = &s[0..5];
let world = &s[6..11];
  • 他の言語は配列は実質参照渡しだった気がした → Rustでは明示的にやってるということ?
Haruka MiyataHaruka Miyata
  • 不変としてfirst_wordに渡してるので、返り値は不変参照となるため変更できない
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!

fn first_word(s: &String) -> &str { }
Haruka MiyataHaruka Miyata
  • 文字列リテラルの型は&str
    • &strは不変参照なので文字列は不変
    • 文字列リテラルは「スライス」
let s = "Hello, world!";
// String型
let my_string = String::from("hello world");
// &str型
let my_string_literal = "hello world";
  • String str スライス 関係性が微妙にわかってない
  • リテラル文字列指定 == &str
Haruka MiyataHaruka Miyata

構造体

  • 名前指定なので柔軟性が高い
  • ↔︎タプルは番号指定
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
// 明示的に与えられてないフィールドは同じ値が入る
let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};
Haruka MiyataHaruka Miyata

タプル構造体

  • ColorPointは違う型
  • structは型を作っている
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// 異なるタプル構造体からインスタンスを生成
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
Haruka MiyataHaruka Miyata

ユニット様構造体

  • フィールドのない構造体
  • Cに無名構造体みたいなのあった気がする
Haruka MiyataHaruka Miyata

構造体データの所有権

  • 構造体が有効な期間は全フィールドが有効であって欲しい
  • 参照型のフィールドを持ちたいときはライフタイム機能を使う必要がある
  • 下記はコンパイルエラー
struct User {
    username: &str,
    email: &str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}
Haruka MiyataHaruka Miyata
  • 変数間の関係性にも意味づけすべき、みたいな倫理観がある
    • Rustがそうなのか、実は他のプログラミング言語にも共通した価値観?
Haruka MiyataHaruka Miyata

メソッド記法

  • methodsとfunctionsがある
  • 書き方は一緒だがメソッドは構造体の中で呼ばれる
  • 最初の引数はselfで構造体自身を指す。
    • 所有権を奪わずに参照だけ欲しい
    • 書き込みしたいなら所有権を奪う形で&mut selfになる。
  • implでとる
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}
Haruka MiyataHaruka Miyata
  • 関連関数:implブロック内にあるが&selfをとらない
    • 構造体に関連づいている
    • 関数でありメソッドではない
    • メソッドであるもの:対象となる構造体のインスタンスが存在するかどうか
    • コンストラクタによく使われる
    • メソッドとの違い
      • methodはクラスメソッド?
      • functionがインスタンスメソッド?
Haruka MiyataHaruka Miyata

列挙型

  • 構造体とどちらを採用すべきか
  • どれか一つの状態をとるものは列挙
  • 構造体同様、メソッド定義できる
  • ::名前空間
    • 列挙型は識別子のもとに名前空間わけされている
    • 列挙子は、[識別子]型
enum IpAddrKind {
    V4,
    V6,
}

fn route(ip_type: IpAddrKind) { }

route(IpAddrKind::V4);
route(IpAddrKind::V6);
Haruka MiyataHaruka Miyata

State: 内的。自身の性質を表現?自然?
Status: 外的。自身が周囲に与える価値を表現?人工的?

Haruka MiyataHaruka Miyata

いかに表現方法を定義するか

// 素朴な例
enum IpAddrKind {
    V4,
    V6,
}

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

let home = IpAddr {
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};

// 列挙子に紐付ける方法
enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
Haruka MiyataHaruka Miyata

nullがない

  • 型を確実に特定できる
  • 値不在の概念
    • 既に組み込まれてるので、Some None が使える
  • ジェネリック型引数<T>: あらゆる型のデータを1つ持てることを意味する
enum Option<T> {
    Some(T),
    None,
}
let x: i8 = 5;
let y: Option<i8> = Some(5);
// 実際に使うにはOption<T>をTに変換する必要がある
// let y = 5;
let sum = x + y;

6 | let sum = x + y;
  |             ^ no implementation for `i8 + Option<i8>`
  |
  = help: the trait `Add<Option<i8>>` is not implemented for `i8`
Haruka MiyataHaruka Miyata

match

  • 型はなんでもよい。↔︎ if は bool のみ
  • アームに紐づくのは式
fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        },
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
Haruka MiyataHaruka Miyata

公開設定

  • 構造体
    • 構造体の可視性に関わらず要素はデフォルト全pri
    • pubにしたい要素だけ指定
  • enum
    • enumの可視性が全フィールドに適用
Haruka MiyataHaruka Miyata

コレクション

  • ベクタ型:可変長の値を並べて保持
  • 文字列:String とか
  • ハッシュマップ:key: value
Haruka MiyataHaruka Miyata

ベクタ型

  • 複数の値をメモリ上に隣り合わせで保持
  • 同時に代入しない時は、コンパイラに値の型を知らせるためジェネリクス指定が必要
  • push, get
let v: Vec<i32> = Vec::new();
// 与えられた型をベクタに変換
let v = vec![1, 2, 3];

要素サイズ知らなくていいのか?

  • pushしたらメモリ足りなくなったときは、新しい場所にコピーして元のメモリ領域は解放される
  • push以降は参照が無効になる(コンパイルエラー)
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);

値変更できる

  • 可変値を参照として渡す
  • 変更するには参照外し演算子*を使う
let mut v = vec![100, 32, 57];
for i in &mut v { *i += 50; }

違う型を保持したいときはEnumを使う

  • なんでも入る状態だとコンパイルがエラーを検知できない
enum SpreadsheetCell {
   Int(i32),
   Float(f64),
   Text(String),
}

let row = vec![
   SpreadsheetCell::Int(3),
   SpreadsheetCell::Text(String::from("blue")),
   SpreadsheetCell::Float(10.12),
];
Haruka MiyataHaruka Miyata

ジェネリック型

  • Tなら任意の型を取れる
  • implのメソッド定義、Tでも特定の型に限るのもどっちもいける
struct Point<T> {
    x: T,
    y: T,
}

// impl 直後の<T>の必要性
// Point<ここが具体的な型でないことを伝えたい>
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// こっちはPoint<具体的な型>なので上記対応は不要
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };
    // let q = Point { x: 5.0, y: 10.0 };
    println!("p.x = {}", p.x());
    // pはf32じゃないのでエラー
    // qならいける
    println!("p.x = {}", p.distance_from_origin());
}
Haruka MiyataHaruka Miyata
struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    // other(渡される側)は違う型の可能性あるから、違うやつ指定しないとだめ
    // 型不明な分だけ渡す
    // other: Point<V, &str>なら,  mixup<V>でOK
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
Haruka MiyataHaruka Miyata

Trait

  • モジュール的なやつ?
  • 継承をなくした?
    • デザインパターンであった気がする:継承よりもComposition
Haruka MiyataHaruka Miyata

各型に実装要求

//  trait
impl Summary for NewsArticle { }
// method
impl NewsArticle  { }

デフォルト実装

pub trait Summary {
    fn summarize(&self) -> String {
    }
}

トレイト境界構文

  • 特定のトレイトを実装する型を許容
pub fn notify(item1: &impl Summary, item2: &impl Summary) { }
//  複数の場合
pub fn notify(item: &(impl Summary + Display)) { }
//  ジェネリック型の場合
pub fn notify<T: Summary + Display>(item: &T) {
Haruka MiyataHaruka Miyata

ライフタイム注釈

  • ライフタイム異なるとバグるやつには指定が必要
  • 参照返す場合は引数と一致させる
    • 関数抜けるタイミングで消えるため
Haruka MiyataHaruka Miyata
  • 所有権の移動の回避に clone 使える
    • ライフタイム考慮不要になる
  • 返り値、タプルではなく構造体使う
    • 意味を持たせる
    • 呼び出し側で分割代入みたいなことしてたら、置き換え考える
    • Rubyでいうインスタンス化
fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();
    let filename = args[2].clone();

    Config { query, filename }
}
Haruka MiyataHaruka Miyata

コンストラクタ

  • Class == 構造体
  • newを定義する
impl Config {
    fn new(args: &[String]) -> Config {
        // バリデーション
        if args.len() < 3 {
            panic!("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();
        // インスタンス返す
        Config { query, filename }
    }
}
impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();

        Ok(Config { query, filename })
    }
}
Haruka MiyataHaruka Miyata

例外処理(独自エラー実装)

  • unwrap_or_else
let config = Config::new(&args).unwrap_or_else(|err| {
    println!("Problem parsing arguments: {}", err);
    process::exit(1);
});
Haruka MiyataHaruka Miyata
  • Box<dyn Error>>
    • トレイトオブジェクト
    • 関数がErrorトレイトを実装する型を返すことを意味する
    • 戻り値の型を具体的に指定しなくても良い
      • エラーケースによって異なる型のエラー値を返せる
      • dynamic
fn run(config: Config) -> Result<(), Box<dyn Error>> {