Memorandum of Rust Documentation

Apr 28, 2017   #Rust 

目次

はじめに

 Rust 1.6のドキュメント1の覚書。半年前に読んだ内容が思い出せないので、まとめておく。振り返り用途を想定しているので、ドキュメントを読んだことがなくてもわかるような書き方はしていないが、どのように理解したかの補足説明を記載している。またいくつかの項目はメモするほどでは無い、または、十分な理解がないため記載していない。それらの項目は、今後更新していく予定。

Install Rust with rustup

 rustupはRust言語のツールチェイン(rustc, cargo, rustupなど)をインストールし、バージョン管理してくれるインストーラー。

$ curl https://sh.rustup.rs -sSf | sh

Cargo

 Cargoは、crate(Rust言語のパッケージ/ライブラリ)の依存管理や、ビルド/テストを行うツール。

プロジェクト作成

$ cargo new PROJECT_NAME --bin

 ライブラリの場合はbinオプション不要。

Build, Run and Release Build

$ cargo build
$ ./target/debbug/PROJECT_NAME # 実行

 以下が上記と同等。

$ cargo run
$ cargo build --release # リリース用に最適化コンパイル
$ cargo ./target/release/PROJECT_NAME # 実行

Config

 dependenciesに依存するcrateを書く。

$ cat ./Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]

[dependencies]
time = "0.1.12"
regex = "0.1.41"

Tutorial

数当てゲーム

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    // https://crates.io/crates/rand
    // gen_rangeは[Low, High)で乱数を生成、引数が整数ならu32を返す
    let secret_number = rand::thread_rng().gen_range(1, 101);

    loop {
        println!("Please input your guess.");

        // Stringは可変長文字列、newはstatic method
        let mut guess = String::new();

        // read_lineは標準入力から引数のバッファへ読み込み、Resultを返す
        // Resultはenum型のOkかErr
        // expectはOkでない場合にpanic!
        io::stdin().read_line(&mut guess)
            .expect("failed to read line");

        // trimでUnicode White_Space(この場合改行)を削除し
        // parseで数値に変換(型注釈でu32)
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            // Orderingは3種のEnumを持つ
            Ordering::Less    => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal   => {
                println!("You win!");
                break;
            }
        }
    }
}

食事する哲学者

use std::thread;
use std::time::Duration;
use std::sync::{Mutex, Arc};

// leftとrightはベクタのindexであるため、可変長整数のusizeを使う
// size(usize/isize)は、マシンのポインタサイズに依存する型
struct Philosopher {
    name: String,
    left: usize,
    right: usize,
}

impl Philosopher {

    // strは静的に、Stringは動的に確保される
    // Stringを引数型にした場合、呼び出しもとでの変換と、コピーが発生
    // newは特別ではない
    fn new(name: &str, left: usize, right: usize) -> Philosopher {
        Philosopher {
            name: name.to_string(),
            left: left,
            right: right,
        }
    }

    // &selfは特別。これが引数に指定された場合はメソッド
    fn eat(&self, table: &Table) {
        // Mutexを保持中のthreadがpanicした場合、
        // lockは失敗する可能性があるが、今回はない
        let _left = table.forks[self.left].lock().unwrap();
        let _right = table.forks[self.right].lock().unwrap();

        println!("{} is eating.", self.name);

        thread::sleep(Duration::from_millis(3000));

        println!("{} is done eating.", self.name);
    }
}

// Mutexはthreadをブロックする共有データであるため、
// ある時刻にアクセスできるのは、1 thread
struct Table {
    forks: Vec<Mutex<()>>,
}

fn main() {
    // vec!は可変長な動的配列
    // tableをArc(Atomically Reference Count)型でラップ
    let table = Arc::new(Table { forks: vec![
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
        Mutex::new(()),
    ]});

    let philosophers = vec![
        Philosopher::new("Judith Butler", 0, 1),
        Philosopher::new("Gilles Deleuze", 1, 2),
        Philosopher::new("Karl Marx", 2, 3),
        Philosopher::new("Emma Goldman", 3, 4),
        Philosopher::new("Michel Foucault", 0, 4),
    ];

    // into_iterで生成したイテレータをmapする
    // 型プレースホルダ_は、コンパイラに型推論を明示
    let handles: Vec<_> = philosophers.into_iter().map(|p| {
        // Rustのimmutableは、複数参照が安全であるかを意味している
        // Arcはcloneによりリファレンスカウントが更新されるが、
        // cloneにより&Tを各スレッドに配れる
        let table = table.clone();

        // spawnは引数のクロージャを新しく生成したスレッドで実行する
        // 今回はpの所有権をmoveしたいので、moveクロージャを使う
        thread::spawn(move || {
            p.eat(&table);
        })
    // mapの結果からコレクションを作成
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

Syntax and Semantics

変数束縛

 Rustの変数束縛は型推論可能で、デフォルトで不変。スコープはブロックで、シャドーイング可能。

// 型注釈有りで可変変数
let mut x: i32 = 8;
{
    println!("{}", x); // 8
    // ブロック内で有効な変数束縛
    let x = 12;
    println!("{}", x); // 12
}
println!("{}", x); // 8
// シャドーイングで、xを不変u32型に再束縛
let x =  42;
println!("{}", x); // 42

関数

 Rustでは、関数の引数は型推論できない。returnで明示せずとも最後の式が戻り値になる。

fn plus_one(i: i32) -> i32 {
    // セミコロンがないので式
    i + 1
}

// 関数ポインタ型の型注釈
let f: fn(i32) -> i32 = plus_one;
// 型推論
let f = plus_one;

f(5); // 6

プリミティブ型

説明
bool true/false
i8 8 bitのinteger
i16
i32
i64
u8 8 bitのunsigned integer
u16
u32
u64
isize マシン依存で可変長なinteger
usize
f32 32 bitの浮動小数点数
f64
char シングルクオートで定義できるUnicode文字
str 文字配列のスライス。Stringと違い静的確保
[T, N] N個のT型値を持つ配列。アクセス時に境界チェックで、存在しない値はエラー。
&[T] T型へのスライス(参照/ビュー)
(T1, .., TN) それぞれT1, …, TNの型を持つ値のタプル
fn(T1, …, TN) -> T T1, …, TNの型を持つ引数を受け取り、T型を返すを関数
let ary:[i32; 5] = [0, 1, 2, 3, 4];
let complete = &ary[..];
let middle = &ary[1..4];

let tuple = (1, 2, 3);
let x = tuple.1;

fn foo(x: i32) -> i32 { x }
let x: fn(i32) -> i32 = foo;

コメント

// 通常のコメント

/// ## ドキュメンテーションコメント
/// - コメントでは、Markdownが利用できる

//! ## ドキュメンテーションコメント2
//! その後に続く要素ではなく、このコメントを含む要素に対するコメント
//! クレートルートlib.rsやモジュールルートmod.rsで使用される

if式

let x = 5;
// if式は値を返すので、yはi32型の10(if/elseの型の一致に注意)
// elseがない場合は、()を返す。
let y = if x == 5 { 10 } else { 15 };

ループ

 Rustのループには、無限ループのloop、条件がtrueである限り繰り返すwhile、イテレータを反復するforがある。 また、breakとcontinueもあり、ループラベルを指定できる。

'outer: loop {
    // 0..10でイテレータを生成し、Iteratorのenumerateメソッドで、indexを得る
    'inner: for (i, n) in (0..10).enumerate() {
        if n  == 5 { break 'outer; }
    }
}

所有権、参照と借用、ライフタイム

 Rustでは、変数束縛letによりあるリソースを変数に束縛した場合、この変数はそのリソースの所有権を持つ。 関数渡しや代入を行った場合所有権はムーブし、所有権を持たない変数を参照するとコンパイラエラーが発生する。

let v = vec![1, 2, 3];
// Vecの所有権はvからv2にムーブする
let v2 = v;
// vは所有権を持たないので、以下のエラーとなる
// error[E0382]: use of moved value: `v`
println!("v[0] is: {}", v[0]);

 また、Rustのプリミティブ型は、Copyトレイトをimplしているため、ムーブではなく値がコピーされる。
 関数渡しや代入後も所有権を維持したい場合には、参照渡しによる所有権の借用がある。 参照には& Tと&mut Tの二種類が存在するが、いづれについても、借用のライフタイムが所有者のライフタイムを超えるとコンパイルエラーとなる。 さらに、「ただひとつの&mut T」か「ひとつの以上の& T」のどちらかのパターンでしか借用できない。

let mut x = 5;
{
    let y = &mut x;
    *y += 1;
}
// ブロックが無い場合以下の行でコンパイルエラー
// &mut i32の借用はprintln!行こうまでライフタイムが続く
// xは、println!で所有権がムーブし、以下の行で解放される
println!("{}", x);

 借用されるすべての参照にはライフタイムがある(値には無い)。関数の場合、すべての参照引数のライフタイムを、ジェネリクスパラメータを用いて指定する必要がある。

// ライフタイム'aの参照& i32を借用する構造体
// この場合Fooは'a以上のライフタイムを持つ
struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let x;                   
                             
    {                        
        let y = &5;          
        let f = Foo { x: y };
        // fのライフタイムを超えるxへの借用
        x = &f.x;            
    }                        
                             
    println!("{}", x);       
}                            

 関数のシグネチャ(引数と戻り値の型)についての型推論が許容されていないのに対して、ライフタイムでは以下の規則に基づいた推論(ライフタイムの省略)が可能。 ただし、入力ライフタイムとは引数のライフタイムで、出力ライフタイムとは戻り値のライフタイムである。

  1. ライフタイムが省略された関数の引数は、互いに異なるライフタイムパラメータを持つ
  2. 入力ライフタイムが1つだけならば、そのライフタイムは省略された全てのライフタイムに割り当てられる
  3. 入力ライフタイムが複数あるが、その1つが &self 又は &mut self であれば、 self のライフタイムは省略された出力ライフタイム全てに割り当てられる

 これらの規則に基づいて推測でき無い場合、コンパイルエラーとなる。

// 規則1により、sとtのライフタイムは異なると推論されるが、
// 出力ライフタイムが決定できない
fn frob(s: &str, t: &str) -> &str; // 省略表記
fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // 推論結果

// 規則2により、入力ライフタイムが参照であるsだけなので、
// 出力ライフタイムは、sと同じになり、決定できる
fn substr(s: &str, until: u32) -> &str; // 省略表記
fn substr<'a>(s: &'a str, until: u32) -> &'a str; // 推論結果

// 規則3により、&mut self参照を引数にもつので、
// 省略された出力ライフタイムは&mut selfと同じになり、決定できる
fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command; // 省略表記
fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // 推論結果

 特別なライフタイムとして'staticがあり、プログラム全体がライフタイムであることを意味する。
 Rustの所有権、参照と借用、ライフタイムは、コンパイル時に解析され実行時にコストが発生しない「ゼロコスト抽象化(zero-cost abstraction)」であり、これにより、データレースやダングリングポインタなどの問題を回避することができる。

ミュータビリティ

 デフォルトでミュータビリティは無く、付与したい場合には明示する必要がある。

// xはミュータビリティを持つ
let mut x = 5;
// yはxへのミュータブルな参照を持ち、y自体もミュータビリティを持つ
let mut y = &mut x;

 Rustでは、イミュータブル=変更不可能ではなく、2箇所以上からの参照が安全である場合がイミュータブルである。 例えばArc<T>の場合、参照カウントを更新しているが(変更が発生しているが)、2箇所以上からの参照が安全であるので、イミュータブルな変数として束縛できる。

use std::sync::Arc;
let x = Arc::new(5);
let y = x.clone();

 ミュータビリティとは変数束縛に関する属性であるため、通常は構造体の特定のフィールドに対するミュータビリティは付与できない。これを実現するためにstd::cellモジュールのような内側のミュータビリティ(interior mutability)がある。 内側のミュータビリティはイミュータブルな変数束縛の場合でも、変更可能である。これに対してArcなどは外側のミュータビリティ(exterior mutability)を持つと言う。

use std::cell::Cell;

struct Point {
    x: i32,
    y: Cell<i32>,
}

let point = Point { x: 5, y: Cell::new(6) };

point.y.set(7);

構造体

 構造体の束縛もデフォルトでイミュータブル。アップデート構文、タプル構造体、Unit-like構造体がある。

struct Point3d {
    x: i32,
    y: i32,
    z: i32,
}
// タプル構造体
struct Inches(i32);
// Unit-like構造体
struct Electron;

let origin = Point3d { x: 0, y: 0, z: 0 };
// アップデート構文
let point = Point3d { z: 1, x: 2, .. origin };
assert_eq!(origin.y, point.y);

let length = Inches(10);
let Inches(integer_length) = length;
assert_eq!(integer_length, 10);

let e = Electron;

列挙型

 Rustのenumは、ヴァリアントと呼ばれる要素のうちのいづれかをとるデータ型を表す。ヴァリアントは、構造体のような方法で定義可能。

enum Message {
    Quit, // Unit-like構造体風
    ChangeColor(i32, i32, i32), // タプル構造体風
    Move { x: i32, y: i32 }, // 構造体風
    Write(String), // タプル構造体風
}

// xの型は、Messageであるが、enum型にアクセスするためには、
// ヴァリアントを明示する必要がある(コンパイルチェック)。
// すなわち列挙型の値は、ヴァリアントに関する情報ももつ(tagged union)。
let x: Message = Message::Move { x: 3, y: 4 };
let y = Message::Write("Hello, world".to_string());

マッチとパターン

 Rustには複雑な条件分岐のためにmatch式(値を返す)がある。match式は、パターン(型と値や条件の組み合わせ)に基づいて=>(アーム)で定義した式を実行する。matchは、コンパイル時に条件の網羅性検査が実施される。また、パターンはmatch意外にも変数束縛などでも利用できる。

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

fn quit() { /* ... */ }
fn change_color(r: i32, b: i32) { /* ... */ }
fn move_cursor_x(x: i32) { /* ... */ }

fn process_message(msg: Message) {
    // 列挙型の全ヴァリアントのパターンを網羅するmatch
    match msg {
        Message::Quit => quit(),
        // structやenumなどの構造化されたデータ型から、要素を取りだす場合に、
        // デストラクチャリング(destructuring)がある。この場合、rとbを取り出している
        // _で束縛された型や値を無視
        Message::ChangeColor(r, _, b) => change_color(r, b),
        // ..は、複数の型や値を無視できる
        // デストラクチャリングの際に、別名も付与可能
        Message::Move { x: myx, .. } => move_cursor_x(myx),
        // refやref mutでリファレンス(参照)を取得できる
        Message::Write(ref s) => println!("{}", s),
    };
}
let x = 'D';
let flag = false;
let msg = "this msg is not showd.";

match x {
    // 数値や文字などれはレンジ(...)がパターンとして利用できる
    'a' ... 'j' => println!("early small letter"),
    // cという変数に@で束縛できる
    c @ 'k' ... 'z' => println!("late small letter: {}", c),
    // |で論理和を表現できる(複式パターン)
    'A' | 'B' => println!("A or B"),
    // if式により条件を付与(いわゆるガード)
    // ここではmsgがシャドーイングされるため、'D'がプリントされる
    msg @ 'D' if !flag => println!("{}", msg),
    // _は任意のケースマッチするパターン
    _ => println!("something else"),
}

メソッド構文

 implブロックを書き、メソッド(self, &self, &mut selfをいづれかを引数に持つ関数)と関連関数(メソッドでない関数、static method、静的メソッド)を実装することができる。 メソッドの意味は見たままだが、イミュータブルな借用である&selfを常用することが推奨される。 Rustではメソッドチェーンを使って、以下の様にBuilderパターンを実装できる。

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct CircleBuilder {
    x: f64,
    y: f64,
    radius: f64,
}

impl CircleBuilder {
    fn new() -> CircleBuilder {
        CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
    }

    fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.x = coordinate;
        self
    }

    fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
        self.y = coordinate;
        self
    }

    fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
        self.radius = radius;
        self
    }

    fn finalize(&self) -> Circle {
        Circle { x: self.x, y: self.y, radius: self.radius }
    }
}

fn main() {
    let c = CircleBuilder::new()
                .x(1.0)
                .y(2.0)
                .radius(2.0)
                .finalize();

    println!("area: {}", c.area());
    println!("x: {}", c.x);
    println!("y: {}", c.y);
}

ベクタ

 vec!マクロで、ヒープに確保される動的配列Vec<T>を作成できる。

// インデックスアクセスにはusize型を使用
let i: usize = 0;
let v = vec![1, 2, 3, 4, 5];
assert_eq!(v[i], 1);

let v = vec![7; 5]; // vec![7, 7, 7, 7, 7]
assert_eq!(v[i], 7);

for i in &v {
    // イミュータブルな借用
    println!("A reference to {}", i);
}

for i in &mut v {
    // ミュータブルな借用
    // *i = n で変更可能
    println!("A mutable reference to {}", i);
}

for i in v {
    // 所有権のムーブ
    println!("Take ownership of the vector and its element {}", i);
}
// vの所有権はムーブしたため、使用不可

文字列

 Rustの文字列には静的確保される&str(strのスライス)と動的確保されるStringがある。いづれもUTF-8のバイトストリームであり、インデックスと文字数は必ずしも一致しない。互い変換可能であるが、&strからStringへの変換はメモリ確保が発生するためコストが高い。

// 文字列リテラルは、&'static strで、実行ファイルに静的確保される
// to_stringメソッドで、文字列リテラルをStringに変換
let hello = "Hello ".to_string();
let world = "world!".to_string();
// Stringと&strは+演算子により結合可能
// StringはDerefによるstrへの型強制を実装しているため、
// &Stringから&strへ型強制される
let hello_world = hello + &world;
for c in hello_world.chars() {
    print!("{}, ", c);
}
assert_eq!('H', hello_world.chars().nth(0).unwrap())

ジェネリクス

 パラメトリック多相(parametric polymorphism)

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn takes_two_things<T, U>(x: T, y: U) {
    // ...
}

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn swap(&mut self) {
        std::mem::swap(&mut self.x, &mut self.y);
    }
}

トレイト

 ジェネリクスに対する型の境界を定義するためにtrait構文がある。トレイトは、スコープ内のもの(useを使うなど)のみ有効であり、トレイトもしくはimplの対象となる型は自分で実装する必要がある。また、トレイト境界が指定されたジェネリック関数は単相化(monomorphization)され、静的ディスパッチ(コンパイル時にインライン化)される。

trait HasArea {
    fn area(&self) -> f64;
}

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

// HasAreaトレイトを実装している型は、必ずareaメソッドを持つ
// トレイトを指定しない場合、これが保証されずコンパイルエラーとなる
fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

 複数のトレイト境界を+により定義でき、where節により簡潔に表現可能。

use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
    x.clone();
    y.clone();
    println!("{:?}", y);
}

fn bar<T, K>(x: T, y: K)
    where T: Clone,
          K: Clone + Debug {

    x.clone();
    y.clone();
    println!("{:?}", y);
}

 デフォルトメソッドの指定も可能。

trait Foo {
    fn is_valid(&self) -> bool;

    fn is_invalid(&self) -> bool { !self.is_valid() }
}

 トレイトの継承も可能で、以下の場合、FooBarの実装者Fooも実装する必要がある。

trait Foo {
    fn foo(&self);
}

trait FooBar : Foo {
    fn foobar(&self);
}

 繰り返し現れるトレイトを実装するのは煩雑なので、以下のトレイトに限り、自動的にトレイトを実装するための アトリビュートが提供されている。

  • Clone
  • Copy
  • Debug
  • Default
  • Eq
  • Hash
  • Ord
  • PartialEq
  • PartialOrd
#[derive(Debug)]
struct Foo;

fn main() {
    println!("{:?}", Foo);
}

if/while let

 Option<T>型のパターンマッチは、if letやwhile letで簡潔に記述できる場合がある。

let option = Some(5);
fn foo(x: i32) { }

match option {
    Some(x) => { foo(x) },
    None => {},
}

// 上記は以下の様に書ける
if let Some(x) = option {
    foo(x);
}
let mut v = vec![1, 3, 5, 7, 11];
loop {
    match v.pop() {
        Some(x) =>  println!("{}", x),
        None => break,
    }
}

// 上記は以下の様に書ける
let mut v = vec![1, 3, 5, 7, 11];
while let Some(x) = v.pop() {
    println!("{}", x);
}

トレイトオブジェクト

 ポリモーフィズムを解決することをディスパッチと呼び、Rustにはコンパイル時に行う静的ディスパッチと実行時に行う動的ディスパッチの両方がある。 実行ファイルは大きくなるが、インライン化される静的ディスパッチの方が通常パフォーマンスに優れる。 ただし、コンパイルは最適化に失敗するケースもあるので、動的ディスパッチが望ましい場合もある。
 トレイト境界を使った関数の場合、そのトレイトの各impl対象の型バージョンの関数に静的ディスパッチする。これに対して、動的ディスパッチを利用するためにRustには、&FooやBox<Foo>の様に型境界を定義するトレイトオブジェクト(型消去)がある。トレイトオブジェクトは、キャストや型強制で利用できる。

trait Foo { fn method(&self) -> String; }
// Fooトレイトをu8とStringで実装
impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }

// トレイト境界にFooではなく、&Fooをとることで、
// キャストや型強制による動的ディスパッチが可能
fn do_something(x: &Foo) {
    x.method();
}

fn main() {
    let x = 5u8;
    do_something(&x as &Foo);

    let x = "Hello".to_string();
    do_something(&x);
}

 トレイトオブジェクトは、オブジェクト安全(object-safe)なトレイトのみで可能である。オブジェクト安全であるとは、以下の2つの条件を満たすことである。

  1. トレイトがSelf: Sized(コンパイル時にサイズを要求するトレイト)を要求しない
  2. メソッドがオブジェクト安全である。すなわち以下のいずれかを満たす
    • 各メソッドが Self: Sizedを要求する
    • 型パラメータを持たず、Selfを使用しない

クロージャ

 Rustでは、|引数|式という形式でクロージャを定義できる。クロージャでは、定義時のスコープ中の変数の借用を得ることができる。匿名関数(無名関数)の様なクロージャの構文は、Fn、FnMut、FnOnceトレイトの糖衣構文である。 また、サイズを特定しライフタイムの省略が不可であることから、クロージャを返す関数を定義する際には、Box<T>でラップする必要がある。

let mut num = 5;
let plus_num = |x: i32| x + num;
// numはplus_numに借用されいるため、コンパイルエラー
let y = &mut num;

 また、moveクロージャを使用することで、所有権をムーブできる。

let num = 5;
// i32はCopyを実装しているので、値のコピーがクロージャ内のnumに束縛される
let owns_num = move |x: i32| x + num;

クレートとモジュール

 Rustはクレート(パッケージ)をCargo(パッケージマネージャー)で、管理する。クレートは複数のモジュール(モジュール名はlower_snake_caseでつけるのが慣習)で構成できる。例えば、以下の構成を考える。このディレクトリにはライブラリクレートと、バイナリクレートがある。

$ cargo new phrases
$ cd phrases
$ touch src/main.rs
$ tree .
...
├── src
│   ├── english
│   │   ├── farewells.rs
│   │   ├── greetings.rs
│   │   └── mod.rs
│   ├── japanese
│   │   ├── farewells.rs
│   │   ├── greetings.rs
│   │   └── mod.rs
│   ├── main.rs
│   └── lib.rs
...

 Rustではデフォルトで全てがプライベートで定義されるため、ライブラリや関数、構造体とそのメンバ等に対してpubキーワードを使う。modでモジュール名を指定した場合、「./モジュール名.rs」か「./モジュール名/mod.rs」が存在することをコンパイラは期待する。

// src/lib.rs
pub mod english;
pub mod japanese;
// src/english/mod.rs
pub mod greetings;
pub mod farewells;

 useキーワードを指定して再エクスポートすることでフォルダ構成とは違うインターフェースも提供できる。

// src/japanese/mod.rs
pub use self::greetings::hello;
pub use self::farewells::goodbye;
mod greetings;
mod farewells;
// src/main.rs
// asで別名をつけることができる
extern crate phrases as sayings;

// useでも同様に別名が利用できる
use sayings::japanese::greetings as ja_greetings;
use sayings::japanese::farewells::*;
use sayings::english::{self, greetings as en_greetings, farewells as en_farewells};

fn main() {
    println!("Hello in English; {}", en_greetings::hello());
    println!("And in Japanese: {}", ja_greetings::hello());
    println!("Goodbye in English: {}", english::farewells::goodbye());
    println!("Again: {}", en_farewells::goodbye());
    println!("And in Japanese: {}", goodbye());
}

constとstatic

 いづれもプロセス全体のライフタイムを持ち、静的である。constはインライン化されるがstaticはされない。またstaticの束縛はミュータブルにできるが、安全では無い。

const N: i32 = 5;
static N: i32 = 5;
static mut N: i32 = 5;

アトリビュート

 アトリビュートは、宣言を修飾するものでtestやderiveなどいろいろある。宣言方法は二種類で、直後の宣言へのアトリビュートと、ブロックで包含する宣言へのアトリビュートである。それ以外の違いは無い。

#[foo]
struct Foo;

mod bar {
    #![bar]
}

typeエイリアス

 typeキーワードで、型名にエイリアスを付与できる。

use std::result;

enum ConcreteError {
    Foo,
    Bar,
}

type Result<T> = result::Result<T, ConcreteError>;

型間のキャスト

 asは安全で、transmuteは安全では無い。いくつかのケースでは暗黙的に型強制されるがasで明示できる。

関連型

 typeを用いて、型のグループである関連型を定義できる。以下の例では、distanceに不要なジェネリックパラメータが必要である。この場合、distanceにジェネリックパラメータEは不要である。

trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
}
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { 42 }

 typeを用いて、関連型を定義すると、シンプルに記述できる。

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}
fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { 42 }

 トレイトオブジェクトを作るためには、関連型の情報をパラメータとして渡す必要がある。

// graphは、Graphをimplしたstructとする
let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;

サイズ不定形

 Rustではほとんどの型は、コンパイラ時にサイズが定る。サイズ不定形はポインタ経由でのみ操作可能で、変数や引数ではサイズ不定形を用いることはできないが、structの最後のフィールドのみ可能。 サイズ不定形の動的サイズ型は、以下の様に利用できる。

//?はSizedという境界をはずすための特別なもの
struct Foo<T: ?Sized> {
    f: T,
}

演算子とオーバーロード

 Rustでは、オペレータトレイトstd::opsで定義されている実装をオーバーロードすることで、いわゆる演算子オーバーロードが可能。またこれらは、トレイト境界としても利用できる。

Derefによる型強制

 Derefトレイトを実装することで、参照外し演算子*をオーバーライドすることができる。また、Deref<Target=T>を実装している型Uは、&Uから&Tへ型強制される。

use std::ops::Deref;

struct DerefExample<T> {
    value: T,
}

// Derefを実装
impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.value
    }
}

// &DerefExample<T>から&Tへの型強制
fn test_deref(c: &char) {}

fn main() {
    let x = DerefExample { value: 'a' };
    assert_eq!('a', *x);
    test_deref(&x);
}

マクロ

 Rustは健全なマクロを持っている。マクロはコンパイラの初期段階で展開され、強力な抽象化を提供している。また、マクロは再帰的である。代表的なマクロにはvec!があり、以下の様に定義されている。

macro_rules! vec {
    // =>は、()の中を、{}の中に置き換える
    // $(...),*は、,で区切られた0回以上の...にマッチするマッチャである
    // $x:exprは任意のRust式にマッチし、変数$xに束縛する
    ( $( $x:expr ),* ) => {
        // マクロはブロックを返す
        {
            let mut temp_vec = Vec::new();
            // $(...)*は繰り返しを意味する
            $(
                //マッチした,で区切られた各要素をプッシュする
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

 このようなマクロ定義により、以下の式は、

let x: Vec<u32> = vec![1, 2, 3];

 以下の様に展開される。

let x: Vec<u32> = {
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
};

 vec!の他にはassert!関連やpanic!、try!、unreachable!、unimplemented!などがある。

Rawポインタとunsafe

 *const T と *mut TはRawポインタと呼ばれる。Rawポインタの生成は安全であるが、参照外しでは安全では無い(未定義の動作が起こりうる)ためunsafeをつけなければならない。RawポインタはFFIで使われる。

let x = 5;
let raw = &x as *const i32;

let points_at = unsafe { *raw };

println!("raw points at {}", points_at);

Effective Rust

スタックとピープ

 Rustでは基本的にメモリはスタック領域に確保するが、メモリを異なる関数間や、関数より長いライフタイムで保持したい場合がある。この場合、Box<T>を使用すうことでメモリをヒープ上で確保できる。また、BoxのDropトレイトの実装が、メモリをデアロケートしてくれる。

テスト

 単体テスト、結合テスト、ドキュメンテーションテストのための仕組みが、Rustには存在する。

$ tree
.
├── src
│   └── lib.rs
~~~
└── tests
    └── lib.rs

 単体テストtestアトリビュートにより定義する。テスト関数はpanicしない限り成功したとみなされる。一方で、should_panicアトリビュートを指定することでpanicした場合に成功させることができる(expectedパラメタで指定した文字列を含むpanicのみ成功とみなす)。また、ignoreアトリビュートを指定した関数は– –ignoredを指定しない限り、テストが実行されない。さらにcfgアトリビュートを付与したテストモジュールは、テスト時のみコンパイルされる。
 ドキュメンテーションテストは、コメント内のコードブロックに記述する。ドキュメンテーションテストは、ライブラリクレートの場合にだけ、実行される。

//! src/lib.rs
//! `adder`クレートはある数値を数値に加える関数を提供する
//!
//! # Examples
//!
//! ```
//! assert_eq!(4, adder::add_two(2));
//! ```

/// この関数は引数に2を加える
///
/// # Examples
///
/// ```
/// use adder::add_two;
///
/// assert_eq!(4, add_two(2));
/// ```
pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    #[should_panic(expected = "assertion failed")]
    fn it_works2() {
        assert_eq!(3, add_two(3));
    }

    #[test]
    #[ignore]
    fn expensive_test() { }
}

 結合テストは、testsディレクトリ以下に異なるクレートとして記述する。これにより他のプログラムと同じ立場でテスト可能。

// tests/lib.rs
extern crate adder;

#[test]
fn it_works() {
    assert_eq!(4, adder::add_two(2));
}

 テストは以下の様に実行でき、ignoreを指定した場合は、ignoreアトリビュートを指定した関数も実行される。

$ cargo test -- --ignored

ドキュメント

 以下のコマンドで、ドキュメンテーションコメントから、ドキュメントを生成できる。

$ cargo doc

イテレータ

 Rustでは、レンジやiterメソッドによりイテレータを生成できる。イテレータは遅延評価され、nextメソッドを呼び出すことで次の要素を生成しOption型で返却する。イテレータは、for文などの他にも、コンシューマ(イテレータに作用し何らかの値を返却する)やイテレータアダプタ(イテレータからイテレータを生成する)で処理できる。

let sum = (1..10)                       // レンジで(1,10)のイテレータを生成
          .map(|x| x * x)               // クロージャの結果からイテレータを生成
          .filter(|&x| x % 2 != 0)      // クロージャがtrueを返す要素からなるイテレータを生成
          .take(3)                      // 先頭のn要素からなるイテレータを生成
          .fold(0, |sum, x| sum + x);   // 初期値と、accumulatorと要素を引数とするクロージャを受け,
                                        // 返り値を次のaccumulatorとして反復する
assert_eq!(35, sum)

 この他にも、collectやfindなどのコンシューマがよく使われる。

平行性

 shared mutable stateは、多くの言語でデータ競合などの問題を引き起こす原因であるが、RustではSendとSyncトレイトと所有権のシステムでこの問題を解決している(コンパイルチェックする)。Sendはスレッド間で安全に受け渡し可能な所有権を持てる型であることを保証し、Syncは複数スレッドから参照が安全であることを保証する。

use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;

fn main() {
    // Arc<T>で所有権の複数参照を可能にする。TはSyncを実装している。
    // MutexはSyncを実装していて、排他制御を行う
    let data = Arc::new(Mutex::new(0));

    let (tx, rx) = mpsc::channel();

    for _ in 0..10 {
        let (data, tx) = (data.clone(), tx.clone());

        // threat::spawnでクロージャからスレッドを生成
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            *data += 1;
            // sendはSendを実装している型を送信できる
            tx.send(()).unwrap();
        });
    }

    for _ in 0..10 {
        rx.recv().unwrap();
    }
}

エラーハンドリング

 即興やサンプルプログラムの実装ではunwrapを使用しても良い(エラーを返す箇所も明示される)。その次に考慮すべきは、try!マクロとResult<T, Box<Error+Send+Sync>>によるエラー処理である。ただしこの方法は型の詳細が不透明になる。理想的なエラーハンドリングは、独自のエラー型を定義する(ErrorとFromの実装)ことである。
 以下では、tryによるエラーハンドリングを示す。

...
[dependencies]
csv = "0.*"
rustc-serialize = "0.*"
getopts = "0.*"
extern crate getopts;
extern crate rustc_serialize;
extern crate csv;

use getopts::Options;
use std::{env, fs, io};
use std::path::Path;
use std::error::Error;
use std::marker::{Sync, Send};

// 世界都市人口CSVの行構造体
// https://github.com/petewarden/dstkdata/blob/master/worldcitiespop.csv
#[derive(Debug, RustcDecodable)]
struct Row {
    country: String,
    city: String,
    accent_city: String,
    region: String,
    population: Option<u64>,
    latitude: Option<f64>,
    longitude: Option<f64>,
}

struct PopulationCount {
    city: String,
    country: String,
    count: u64,
}

fn print_usage(program: &str, opts: Options) {
    println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
}


// 返り値は、Vec<_>かBoxによるErrorのトレイトオブジェクト
fn search<P: AsRef<Path>>
         (file_path: &Option<P>, city: &str)
         -> Result<Vec<PopulationCount>, Box<Error+Send+Sync>> {
    let mut found = vec![];
    // ファイル指定がなければ標準入力を、
    // ファイル指定があり、ファイルが存在すればファイルを、Box<io::Read>としてinputに束縛
    // ファイル指定があり、ファイルが存在しなければ、Errorを返す
    let input: Box<io::Read> = match *file_path {
        None => Box::new(io::stdin()),
        Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), //openはAsRef<Path>を期待
    };
    // from_readerはio::Readを期待
    let mut rdr = csv::Reader::from_reader(input);
    for row in rdr.decode::<Row>() {
        let row = try!(row);
        match row.population {
            None => { }
            Some(count) => if row.city == city {
                found.push(PopulationCount {
                    city: row.city,
                    country: row.country,
                    count: count,
                });
            },
        }
    }
    if found.is_empty() {
        Err(From::from("No matching cities with a population were found."))
    } else {
        Ok(found)
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
    opts.optflag("h", "help", "Show this usage message.");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => { m }
        Err(e) => { panic!(e.to_string()) }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }

    let file = matches.opt_str("f");
    let data_file = file.as_ref().map(Path::new);

    let city = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    match search(&data_file, &city) {
        Ok(pops) => {
            for pop in pops {
                println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
            }
        }
        Err(err) => println!("{}", err)
    }
}

 次は、独自のエラー型によるエラーハンドリングを示す。

extern crate getopts;
extern crate rustc_serialize;
extern crate csv;

use getopts::Options;
use std::{env, fs, io, fmt};
use std::path::Path;
use std::error::Error;

// 世界都市人口CSVの行構造体
// https://github.com/petewarden/dstkdata/blob/master/worldcitiespop.csv
#[derive(Debug, RustcDecodable)]
struct Row {
    country: String,
    city: String,
    accent_city: String,
    region: String,
    population: Option<u64>,
    latitude: Option<f64>,
    longitude: Option<f64>,
}

struct PopulationCount {
    city: String,
    country: String,
    count: u64,
}

#[derive(Debug)]
enum CliError {
    Io(io::Error),
    Csv(csv::Error),
    NotFound,
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CliError::Io(ref err) => err.fmt(f),
            CliError::Csv(ref err) => err.fmt(f),
            CliError::NotFound => write!(f, "No matching cities with a \
                                             population were found."),
        }
    }
}

impl Error for CliError {
    fn description(&self) -> &str {
        match *self {
            CliError::Io(ref err) => err.description(),
            CliError::Csv(ref err) => err.description(),
            CliError::NotFound => "not found",
        }
    }
}

impl From<io::Error> for CliError {
    fn from(err: io::Error) -> CliError {
        CliError::Io(err)
    }
}

impl From<csv::Error> for CliError {
    fn from(err: csv::Error) -> CliError {
        CliError::Csv(err)
    }
}

fn print_usage(program: &str, opts: Options) {
    println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
}

// 返り値は、Vec<_>かCliError
fn search<P: AsRef<Path>>
         (file_path: &Option<P>, city: &str)
         -> Result<Vec<PopulationCount>, CliError> {
    let mut found = vec![];
    let input: Box<io::Read> = match *file_path {
        None => Box::new(io::stdin()),
        Some(ref file_path) => Box::new(try!(fs::File::open(file_path))),
    };
    let mut rdr = csv::Reader::from_reader(input);
    for row in rdr.decode::<Row>() {
        let row = try!(row);
        match row.population {
            None => { } // スキップする
            Some(count) => if row.city == city {
                found.push(PopulationCount {
                    city: row.city,
                    country: row.country,
                    count: count,
                });
            },
        }
    }
    if found.is_empty() {
        Err(CliError::NotFound)
    } else {
        Ok(found)
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
    opts.optflag("h", "help", "Show this usage message.");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => { m }
        Err(e) => { panic!(e.to_string()) }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }

    let file = matches.opt_str("f");
    let data_file = file.as_ref().map(Path::new);

    let city = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    match search(&data_file, &city) {
        Ok(pops) => {
            for pop in pops {
                println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
            }
        }
        Err(err) => println!("{}", err)
    }
}

おわりに

 公式ドキュメントには、ここで書いていない項目も結構あるので、必要になれば追記するかも。また、内容を理解する上ででRust by Example2も非常に参考になった。

参考

関連記事