105
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust プログラミング・メモ

Last updated at Posted at 2016-09-19

たまにコードを書くと書き方をよく忘れてるのでメモ。Rust v1.11~1.21-nightly
記述のソースとなるリンクも大量に貼る。標準ライブラリの覚え書きも。

必須ドキュメント

その他リンク

参考リンク

ツール

Cargo: packages for Rust

色々行う。

  • プロジェクトを作成/build/run/test/...
  • パッケージを管理
$ cargo new hello_world --bin
$ tree hello_world/
hello_world/
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files
  • 実行形式のプロジェクトを作成
    • --bin を抜くと、ライブラリ・プロジェクトを作成
    • Cargo.toml ファイルにプロジェクトの詳細が記述される
$ cd hello_world
$ cargo run

rustup

rustup はコンパイラ等のバージョンマネージャー。
サイトトップのコマンド curl https://sh.rustup.rs -sSf | sh でインストール。

  • $ rustup update - ツールチェインをアップデート
  • $ rustup show - インストールされているツールチェイン一覧を表示
  • $ rustup install stable - stable チャンネルをインストール
  • $ rustup install beta
  • $ rustup install nightly
  • $ rustup install nightly-YYYY-MM-DD - 指定日の nightly をインストール
  • $ rustup default stable - stable チャンネルをデフォルトに
  • $ rustup default beta
  • $ rustup default nightly
  • $ rustup default nightly-YYYY-MM-DD - 指定日の nightly をデフォルトに

racer

racer は IDE での編集支援ソフト。

  • $ cargo install racer - インストール

Atom.io での使い方は

  1. Atom で racer パッケージをインストール
  2. rust-src コンポーネントを $ rustup component add rust-src でインストール
  3. Atom で racer パッケージの Setting
  4. Path to the Racer executable には、ターミナルで $ echo $HOME/.cargo/bin/racer した結果を、
  5. Path to the Rust source code directory には、ターミナルで $ echo $(rustc --print sysroot)/lib/rustlib/src/rust/src した結果を入力

斯くして Atom 上の .rs ソースファイルで入力補完が効くようになる。

rustfmt

cargo install rustfmt-nightly

キーワード Keywords

1st/2nd/Grm/Jpn/src

用途による大まかな分類。ただし複数の用途を与えられているキーワードもある。

  • キャスト - as
  • 関数・変数・定数定義 - fn, let, const, static
  • 制御構造
    • 分岐 - if, else, match
    • ループ - for, in, loop, while
    • 脱出・続行 - break, continue, return
  • モジュール - crate, extern, use, mod
    • パス - self, super
  • カスタム型 - struct, enum, union
  • トレイト・型 - trait, type, where
  • 実装 - impl, for
    • 型エイリアス - Self
    • 引数 - self
  • 可視性限定子 - pub, in
  • 危険マーカー - unsafe
  • 定数 - true, false
  • クロージャ - move
  • パターン - mut, ref
  • feature(box_patterns), feature(box_syntax) - box
  • feature(catch_expr) - do, catch
  • feature(specialization) - default

なお、default, 'static, union, catch は特定の文脈においてのみ特別な意味を持つ、弱いキーワード (weak keywords) となっている。

参考リンク:Rustにおけるキーワードの役割一覧 - 簡潔なQ

予約キーワード

コメント Comments

Ref/Grm

  • 以下4種のコメントがある。
    • // - お馴染みCスタイルの行コメント
    • /*~*/ - ブロックコメント
      • /* This is /* commented */ out. */ - ネスト可能
    • ///, /**~*/ - ドキュメント用のコメント。
      • 属性 #[doc="..."] と等価。すなわち、続くアイテムを修飾する。
    • //!, /*!~*/ - ドキュメント用のコメント。
      • 属性 #![doc="..."] と等価。すなわち、囲むアイテムを修飾する。普通はモジュールを説明。
  • コメントアウトされた部分は、意味的に空白と等価に扱われる。
    • 以下のマクロ呼び出し結果は、a+b+c+a+b+c+
macro_rules! test (($($x:tt)*) => {$( print!("{}+", stringify!($x)); )*}; );

test!{a b  c}
test!{a/*  */b//
c}

文 Statements

Ref/Grm/src

  • Rust は式言語なので、文は少ない。
  • 文は値を返すような対象ではない。
    • よって、例えば次のlet文/fnアイテムを囲むブロックの値は、文の存在が無視され単にユニット()となる。
let a: () = { let a = 100; };
let b: () = { fn test_fun() -> i32 { 1 } };

letlet statements

Ref/1st/Grm/Jpn/src

  • キーワードletを使って変数(識別子)を値に束縛する。
    • let pat ( : type )? ( = init )?; の形式
      • 型指定 type が無くても、初期値 init や後々代入される値に基づいて、自動的に型推論される。
        • 型推論不可能な場合、エラーが出る。
      • let文で初期値を与えなくても、後で値を与えれば問題ない。
        • 未初期化変数を使用しようとした場合、エラーが出る。[#E0381]
        • unsafestd::mem::uninitialized関数を用いて、初期化に関するコストを避けることが出来るが危険。
    • 既存の変数の再定義が可能。
    • C言語のような変数束縛の並列 (int a=1,b=2; のようなの) はできない。
      • let a = 1, b = 2; は不可。
      • let (a, b) = (1, 2); のようにタプルを使った同時束縛は可能。
let a = 2;
let b: f32 = 10.0;
let c;
let d: u8;
let a = a as f32 + 0.1;
let (a, b) = (a * b, (a + b).to_string());

c = 'c';
d = 10;
println!("{} {} {} {}", a, b, c, d); // 21 12.1 c 10

let e: char;
// println!("{}", e); <- error[E0381]: use of possibly uninitialized variable: `e`
//                ^ use of possibly uninitialized `e`
  • let mut hoge = ...; のように書くと let mut で一塊のように見えるが、実際は mutパターンの一部なので、mut hoge で一塊となっている。
    • よって次のようにも書ける。
let (mut a, ref b) = (10i32, 20i32);
a *= 2;
assert_eq!(a, *b);
  • ワイルドカードパターン_を値に束縛すると、その値は即捨てられる(リソース解放)。
    • 以下のサンプルでは、まず _ に与えられた値が順にDropし、スコープを抜けた後、残った変数が作られた順序とは逆にデストラクタが呼ばれる。
      • ライフタイムが _a $\supsetneq$ _b $\supsetneq$ _c $\supsetneq$ _d であるため。
    • トレイトの実装においてメソッドの引数が不要なとき、ダミーの引数として_を使ったりする。
      • fn foo(_: T) -> bool { false }
  • また識別子名をアンダーライン_から始めると、unused_variablesの警告が出ない。
use std::fmt::Display;
use std::ops::Drop;

struct Foo<T: Display>(T);

impl<T: Display> Drop for Foo<T> {
    fn drop(&mut self) {
        println!("Drop {}", self.0);
    }
}

{
    let _  = Foo("-3");
    let _a = Foo(-2);
    let _b = Foo(-1);
    let _  = Foo('0');
    let _c = Foo('1');
    let _d = Foo('2');
    let _  = Foo(3.0);
    println!("-- End of Scope --");
}

Stdout is

Drop -3
Drop 0
Drop 3
-- End of Scope --
Drop 2
Drop 1
Drop -1
Drop -2
  • 型が不明な変数について、let a: () = を使えば簡単に型を確認することができる。
    • コンパイラーがエラー文として正しい型を教えてくれる。
    • 次の例では、型が std::result::Result<i32, std::num::ParseIntError> であると解る。
use std::str::FromStr;
let a: () = i32::from_str("100");
//          ^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
//   = note: expected type `()`
//              found type `std::result::Result<i32, std::num::ParseIntError>`
  • コンパイラーは賢いので、特に指示する必要の無いジェネリック型のパラメータの一部をワイルドカード_にしても、よろしく取りはからってくれる。
let a = [1, 2, 3];
let doubled: Vec<_> = a.iter().map(|&x| x * 2).collect();
// == let doubled = a.iter().map(|&x| x * 2).collect::<Vec<_>>();

assert_eq!(vec![2, 4, 6], doubled);

アイテム Items

Ref/src

  • アイテムとは、ソースコードにおいてアルゴリズム以外の部分、型や外部要素の利用等の宣言を行う文。
  • 大ざっぱに キーワード 識別子 { 本体部分 } の形式が多い。 - fn, trait, impl, mod, ...
    • この場合、末尾にセミコロンは不要。
  • 波括弧{}の代わりに、丸括弧()が可能な場合もある - struct, macro_rules!, ...
    • キーワード 識別子 ( 本体部分 ); の形式。
    • この場合、セミコロン;が必要。
  • 波括弧{}ブロックが無い、場合もある - const, use, extern crate, ...
    • キーワード 識別子+ 本体部分 ; の形式
    • この場合も、セミコロン;が必要。

constアイテム

Ref/1st/Grm/Jpn/src

  • キーワードconstを用いて定数を定義する。
  • const定数名は、全部大文字であることが望ましい。
    • これを破ると警告が出る。#[warn(non_upper_case_globals)]
  • let式と違い、定数の型の明示が必要。
  • const定数は場合により適宜プログラムにインライン展開されるので、定まったアドレスを持たないことに注意。
  • const mut変数を定義することは不可能。

staticアイテム

Ref/1st/Grm/Jpn/src

  • キーワードstaticを用いてグローバル変数を定義する。
  • static変数名は、全部大文字であることが望ましい。
    • これを破ると警告が出る。#[warn(non_upper_case_globals)]
  • let式と違い、またconst定数と同様に、定数の型の明示が必要。
  • static変数は定まったアドレスを持つことに注意。
    • 故に参照は 'static ライフタイムを持つ。
  • static mut変数を定義することも可能
    • 他のスレッドから書き換えられたりする危険性があるので、使用には unsafe が必要
static STATIC_F: f32 = 0.0;
static STATIC_F_REF: &'static f32 = &STATIC_F;
static STATIC_TEXT: &'static str = "a static str";

static mut STATIC_MUT_F: f32 = 0.0;
static STATIC_MUT_F_REF: &'static f32 = unsafe { &STATIC_MUT_F };
unsafe {
    println!("{}", STATIC_MUT_F);
    println!("{}", *STATIC_MUT_F_REF);
    STATIC_MUT_F += 1.0;
    println!("{}", STATIC_MUT_F);
    println!("{}", *STATIC_MUT_F_REF);
}
fn foo<T>() -> *mut i32{
    static mut FOO: i32 = 0;
    unsafe { println!("{}", FOO); FOO += 1; &mut FOO }
}

assert_eq!(foo::<i8>(), foo::<u8>());
assert_eq!(foo::<i8>(), foo::<f32>());
assert_eq!(foo::<i8>(), foo::<char>());
assert_eq!(foo::<i8>(), foo::<&str>());

fnアイテム(関数定義文)

src

  • 関数定義はキーワードfnを用いて行う
  • 関数本体の最後の式が戻り値となる。
    • キーワードreturnを用いて、本体の記述の途中で戻り値を与えることも出来る。
    • returnが使われず、本体部分の末にセミコロン;があると、戻り値はユニット()となる。
  • 関数の本体の中で独自の関数を定義することも出来る(関数内関数)。

View アイテム

参照:クレートとモジュール

  • View(眺め、視界)すなわち、そのソースコードにおいて見えているもの・知っているものを左右する(i.e. 外部ソースで宣言されている関数等を既知とするための)アイテム。以下の2つ。
    • extern crateアイテム - 外部クレートのインポートを宣言する。
    • useアイテム - モジュール/モジュール以下の要素(関数・型・トレイト・定数など)のインポートを宣言する。
      • この「インポートを宣言」とは、ソースコードの宣言以降の部分において、宣言対象が既知となる(未知では無い=名前解決できる)ことを意味する。
  • 両アイテム共に、キーワードasを用いて宣言対象に別名を与えることが出来る。
extern crateアイテム

Ref/1st/Grm/Ex./Jpn/ExJ

useアイテム

Ref/1st/Grm/Ex./Jpn/ExJ

  • mod アイテムには以下の 2 つの機能がある。
    • ローカルスコープに名前をインポートする。
      • use path::name; の形式。
    • ローカルスコープで新しい名前にバインドする。
      • use path::old_name as new_name; の形式。
  • use module_a::module_b::AType::{Self, VariantA, VariantB}; のように複数の名前を同時にインポートできる。
    • ここの Selfuse module_a::module_b::AType; と等価。
  • 参考リンク:Rustで use std; が必要なときとエラーになるときがあるのは何故か - 簡潔なQ

modアイテム

Ref/1st/Grm/Ex./Jpn/ExJ

  • mod アイテムには以下の 2 つの種類がある。
    • 特別なファイル (mod.rs等) において、モジュールの使用を宣言する。
      • mod mod_name; の形式。
    • mod ブロック - ファイル分割無しに、新規モジュールを作成できる。
      • mod new_mod_name { items } の形式。
    • 以上2つ共に、pub修飾子が頭に付くこともある。

その他アイテム

  • externアイテム
  • global_asm!アイテム
  • typeアイテム
  • カスタム型宣言アイテム
    • enumアイテム
    • structアイテム
    • unionアイテム
  • traitアイテム
  • implアイテム
  • マクロ発動アイテム
  • マクロ定義アイテム

式 Expressions

Ref/Grm/src

繰り返し(ループ)式 Loops

Ref/1st/Jpn/src

  • while, loop, for 等々のループ式があるが、いずれも全てユニット型 () のみを返す。1
    • v1.19よりbreakキーワードを用いることで、ユニット型以外も返せるようになった。See RFC 1624.
  • loopループ - Ref/Grm. 無限ループ
    • loop { body } の形式
    • body ブロックにおいて break でループを脱出しないと、無限ループとなる。
  • whileループ - Ref/Grm. 条件ループ
    • while expr { body } の形式
    • 条件式部分で波括弧{,}で括り式を並列させることで、少なくとも1回は body ブロックを実行する、いわゆる do-while ループを再現できる。
      • while { body; expr } {} の形式
        • ダミーの本体ブロックとして、尻に {} が付くことに注意。
  • forループ - Ref/Grm. イテレータ(反復子)ループ
    • for val in expr { body } の形式
    • トレイトstd::iter::IntoIteratorを実装してイテレータへと変換可能な式 expr の要素を走査する
// while
let mut i = 0;
while i < 10 { i += 1; }
assert_eq!(i, 10);

// like do-while
while {
    i += 1;
    i < 10
} {}
assert_eq!(i, 11);

// for
for c in vec!('H','e','l','l','o','!') {
    println!("{}", c);
}

// loop
let a = loop { break; };
let b = loop { break 10; };

assert_eq!(a, ());
assert_eq!(b, 10);

ループ・ラベル Loop labels

Ref/1st/Jpn/src

  • シングルクォート'とコロン:を用いて、各種ループ式にラベルを付けられる。
    • 'label_name: keyword の形式
      • ここで label_name はラベルの名前となる識別子、keywordloop, while, for のいずれかのキーワード。
  • キーワードbreak/キーワードcontinueを用いて、ループの脱出/続行を行う。
    • break; / continue; - この式を囲む最内ループから脱出/続行する。
    • break expr; - break式に値を与えることで、この式を囲む最内ループの戻り値を、ユニット()から expr へと変更できる。
    • break式、continue式にラベル名を与えることで、ラベル名で指名したループからの脱出/続行が行える。
      • break 'foo;
      • break 'foo value; - breakで値を返すときは、ライフタイム-値の順で書く。
      • continue 'bar;
let mut i = 0;
let value = 'loop1: loop {
    'loop2: for j in 0..10 {
        let mut ks = 0..j+1;
        'loop3: while let Some(k) = ks.next() {
            if j % 2 == 0 { continue 'loop2; }
            if k % 2 == 0 { continue 'loop3; }
            println!("{} {} {}", i, j, k);
            i += 1;
            if i > 10 { break 'loop1 i + j + k; }
        }
    }
};
println!("value: {}", value);

マッチ式 match expressions

Ref/1st/Grm/Jpn/src

  • matchキーワードを用いて、パターンマッチを行う。
  • match expr { pattern-list } の形式
  • 各マッチ節が返す値の型は、全て一致しなくてはならない。
    • match x { 10 => "Ten", _ => 0, }; // Error: note: match arm with an incompatible type

パターン Patterns

1st/Jpn/src

  • パターンはmatch式だけでなくlet文でも使われる重要な要素である。
  • 次のmatch式で具体例を参照せよ。
for x in 0..14 {
    let text = match x {
        0 => "zero",
        1 | 2 => "one, two",
        3 ... 5 => "さん, よん, ご",
        x @ 6 ... 7 => match x { 6 => "六", 7 => "七", _ => unreachable!() },
        x @ 8 ... 9 | x @ 10 ... 11 if x%2 == 0 => "捌, 拾",
        8 ... 11 => "nine, eleven",
        _ => ">=12",
    };
    println!("{}: {}", x, text);
}
  • 浮動小数点数をパターンで使うと、警告が出るので注意。
  • | で並列
  • ... で範囲。
    • レンジ式の..と違い、first ... end においては最終要素 end も含まれる (inclusive) なので注意
    • (少なくとも現在では)特定のプリミティブ(数値型や文字型)に対してしか使えないようなので注意。
  • @ で変数に束縛
    • | と同時に使うときは、同じ変数への束縛を再明示
  • if でガード
    • pat1 | pat2 if gud は、pat1 | (pat2 if gud) ではなく (pat1 | pat2) if gud 的な意味なので注意
      • i.e. |if より優先度が高い
  • マッチングは頭から行われる
  • _ はワイルドカード

if letif let expressions

Ref/1st/Grm/Jpn/src

  • 式とパターンマッチを試して、変数束縛を行う。
  • if let pat = expr { body } else_tail ? の形式
    • パターンは等号=の左辺(右辺には置けない)。
  • 常に成立する(マッチに失敗しない)パターンについては、if let式を組むとエラーが出る。[#E0162]
    • この場合let文を使えということだ。
let val = ("tagA", "a body", "tagB");

if let ("tagA", body, _) = val {
    println!("tagA's body: {}", body);
} else {
    println!("unmatch");
}

if let (_, body, "tagB") = val {
    println!("tagB's body: {}", body);
}

while letwhile let expressions

Ref/1st/Grm/Jpn/src

  • ループ式の一種。
  • whileループにおいて、再帰の度に行われる条件式で変数束縛を行う。
    • 毎回 if let を行い、if let に失敗したら再帰終了的な。
let mut x = vec![1, 2, 3];

while let Some(y) = x.pop() {
    println!("y = {}", y);
}
// y = 3, y = 2, y = 1

レンジ式 Range expressions

Ref/Grm/src

  • ..演算子を用いて、範囲を表す構造体を簡単に記述するための記法。
    • 演算子の優先順位の表を見て分かるとおり、..演算子の優先度は低い
      • そのため 1+2*3 .. 10i32.pow(2)-10 のように式を組み合わせたレンジ式において、(1+2*3) .. (10i32.pow(2)-10) のような括弧は不要。
  • レンジ式は 0 .. 00 .. -10 のような startend のような式も受け付けるが、中身は空。
    • assert_eq!(0, (0 .. -10).len());
  • std::collections::rangeモジュールにおいて、より発展的なRangeArgumentトレイトが与えられているが、まだ実験的 (collections_range #30877)

4種のレンジ式

  • start..end - 構造体 std::ops::Range を表す。
    • 半開区間 [start, end)
    • 最終要素 end は含まれない (exclusive) に注意
  • start.. - 構造体 std::ops::RangeFrom を表す。
    • 区間 [start, +∞)
  • ..end - 構造体 std::ops::RangeTo を表す。
    • 区間 (-∞, end)
  • .. - 構造体 std::ops::RangeFull を表す。
    • 全区間 (-∞, +∞)
#![feature(range_contains)]
#![allow(unused_variables)]
use std::ops::*;

let a : Range<i32> = 1..10;
let b : RangeFrom<i32> = 1..;
let c : RangeTo<i32> = ..10;
let d : RangeFull = ..;

assert_eq!(a, Range{ start: 1, end: 10 });
assert_eq!(b, RangeFrom{ start: 1 });
assert_eq!(c, RangeTo{ end: 10 });
assert_eq!(d, RangeFull);

assert!(!a.contains(0) && a.contains(1) && !a.contains(9) && !a.contains(10));
assert!(!b.contains(0) && b.contains(1) &&  b.contains(9) &&  b.contains(10));
assert!( c.contains(0) && c.contains(1) && !c.contains(9) && !c.contains(10));
// assert!( d.contains(0) && d.contains(1) &&  d.contains(9) &&  d.contains(10));
// RangeFull は contains() メソッドを持たなかった。

演算子式 Operator expressions

Ref/src

殆どの演算子はオーバーロード可能。
各種演算子は以下のモジュールで扱われる。

演算子の優先順位 Operator precedence

Ref/src

  • 次の表で、演算子を優先順位降順に並べる。
    • 単項演算子は二項演算子より優先されることが分かる。
      • より正確には、後置単項演算子 > 前置単項演算子 > 二項演算子 の順。
    • 「非結合」演算子では、その結合を丸括弧 (,) で明示しないと文法エラーとなる。
      • a == b == c - Error
      • (a == b) == c - OK
  • フィールド演算子.?より強い。よって &self.hoge?&((self.hoge)?)となる。
演算子 結合性
?
単項 - * ! & &mut
as : 左結合
* / % 左結合
+ - 左結合
<< >> 左結合
& 左結合
^ 左結合
| 左結合
== != < > <= >= 非結合
&& 左結合
|| 左結合
.. ... 非結合
<- 結合
= += -= *= /= %=
&= |= ^= <<= >>=
結合

? 演算子 The ? operator

Ref/std/src

v1.13より導入された、エラー処理用の演算子。try!マクロの代わり。
? 演算子オーバーロード用にstd::ops::Tryトレイトは用意されているが、あくまで実験段階。

std::result::Result<T, E> 型の変数に対して後置で作用し、変数の値が

  • Ok(x) なら、unwrap して x を継続に渡す
  • Err(e) なら、(関数またはクロージャーの戻り値の型に合わせて)Err(From::from(e))returnする。
fn try_to_parse() -> Result<i32, ParseIntError> {
    let x: i32 = "123".parse()?; // x = 123
    let y: i32 = "24a".parse()?; // returns an Err() immediately
    Ok(x + y)                    // Doesn't run.
}

let res = try_to_parse();
println!("{:?}", res);

v1.22より、?演算子がOption<T> 型に対しても作用するようになった。
すなわち OptionTry トレイトを実装した

  • Some(x) なら、x を継続に渡す。
  • None なら、Nonereturnする。
fn option_try_test(opt: Option<i32>) -> Option<i32> {
    let x = opt?;
    Some(x * 2)
}
assert_eq!(option_try_test(Some(10)), Some(20));
assert_eq!(option_try_test(None), None);

try_trait #42327により、Option<T>型に対する?演算子でResult<T,NoneError>型の返り値にも対応する。

  • Some(x) なら、x を継続に渡す。
  • None なら、ユニットライク構造体std::option::NoneErrorErrで包んでreturnする。
#![feature(try_trait)]

use std::option::NoneError;

fn option_try_test2(opt: Option<i32>) -> Result<i32, NoneError> {
    let x = opt?;
    Ok(x * 2)
}
assert_eq!(option_try_test2(Some(10)), Ok(20));
assert_eq!(option_try_test2(None), Err(NoneError));

box

Old/Jpn/src

[Experimental] (box_syntax #27779)

  • キーワード box を用いて、与えられた式をヒープへと直接書き込む box 式を記述する。
    • box expr の形式
    • 巨大な値を効率よく扱うために用いる。
  • 例えば次の文は容易く error: stack overflow を引き起こす。(参考: Rustで巨大なヒープメモリを取る
let buf = Box::new([0; 1024*1024*4]);
  • しかし次のように box 式を用いると、式 [0; 1024*1024*4] の(評価)結果はスタックを経由せず、ヒープへと直接書き込まれるため、スタック・オーバーフローを回避できる
    • そしてこの box 式は、型 Box<[i32; 4194304]> の値を返す。
#![feature(box_syntax)]
let buf = box [0; 1024*1024*4];

プリミティブ型 Primitive Types

Ref/1st/std
以下のプリミティブ型が用意されている。

let a_int: i32 = 10;
let a_num: f32 = 1.0;
let a_boolean: bool = true; // or false
let a_char: char = '❤';
let an_array: [i32; 3] = [1u32, 2, 3];
let a_str: &a_str = "text";
let a_slice: &[i32] = &an_array[0..2];
let a_tuple: (&'static str, i32, char) = ("hello", 5, 'c');
let a_ptr: *const i32 = &10i32;

タプル Tuple types

Ref/1st/Grm/Ex./Jpn

  • タプルの要素には、let文やif let式、ドット記法でアクセスできる。
    • ドット記法の添字は 0 から始まる。
  • 1要素タプルには曖昧さ排除のため、型名・リテラル共にカンマ,が必要。
    • let one_element_tuple: (i32,) = (10,);
let a_tuple: (bool, i32, f32) = (true, 10, 0.0);

let (head, body, _) = a_tuple;
assert_eq!(head, true);
assert_eq!(body, 10);

if let (true, _, last) = a_tuple {
assert_eq!(last, 0.0);
}

assert_eq!(a_tuple.0, true);
assert_eq!(a_tuple.1, 10);
assert_eq!(a_tuple.2, 0.0);

ユニット型 the unit type

  • 0要素タプル型 () は特別に「ユニット型」と呼ばれる。
  • ユニット型の値はユニット () ただ一つ。
  • C言語の void 的な、または空集合。
  • セミコロン;で終わる式文やはユニットを返す。
  • 型のサイズは 0.
    • assert_eq!(size_of::<()>(), 0);

アレイとスライス Array, and Slice types

Ref/Ex.

アレイ / Array / std.primitive.array

スタック上の固定長配列。[T; N]が配列の型。

アレイ式 Array expressions

Ref/Grm

[1, 2, 3, 4];
["a", "b", "c", "d"];
[0; 128]; 

スライス型 / Slice types / std.primitive.slice

  • 隣接 (contiguous) するデータ型への動的サイズの参照(ビュー)。
    • 動的なので型に長さ情報を持たない。
  • &[T]がスライスの型。
スライシング構文 Slicing syntax
// A stack-allocated array
let array: [i32; 3] = [1, 2, 3];

// A heap-allocated array
let vector: Vec<i32> = vec![1, 2, 3];

// A slice into an array
let slice: &[i32] = &vector[..];

ベクタ Vectors

  • std::vec::Vec はヒープ上に確保された配列を扱うジェネリック構造体。
    • プリミティブ型ではない。
  • 生成にはもっぱらマクロ vec! を使う。
  • 参考ソース libcollections/macros.rs
let v1: Vec<i32> = vec![1, 2, 3, 4]; // 要素を列挙

let v2 = vec![0; 5]; // vec![初期値; 個数]
assert_eq!(v2, vec![0,0,0,0,0]);

let mut v: Vec<i32> = Vec::new();
v.push(1); v.push(2); v.push(3); v.push(4);
assert_eq!(v, v1);

文字列 Strings

1st/Grm/Ex./Jpn

  • &str - 文字列のスライス型。
    • &'static str - リテラルにより生成されるプリミティブ型。
  • String - UTF-8 エンコードされた文字列。文字列操作するならもっぱらこれを用いる。

文字列リテラル String literals

let a: &'static str = "foobar";
let b = "foo\
         bar";
assert_eq!(a,b);

Raw 文字列リテラル Raw string literals

  • rに続く n (>=0) 個の#に続く"で始まり、"に続く n 個の#で終わる範囲を文字列とする Raw 文字列リテラル。
    • Raw 文字列リテラルの中にはエスケープが存在しないので、r#""# の中に文字列 "\"#" を入れることは出来ない。
      • 代わりに r##""## を使おう。
let s1 = "text";
let s2 = r"text";
let s3 = r#"text"#;
let s4 = r##"text"##;
assert_eq!(s1, s2);
assert_eq!(s1, s3);
assert_eq!(s1, s4);

バイト文字列リテラル Byte and byte string literals

  • "で括った文字列/Raw 文字列リテラルにbの接頭辞で、バイト文字列になる。
    • 型はu8のアレイ: [u8: n]'static参照。
    • ASCII コードしか含められない。
let s1: &'static [u8; 4] = b"text";
let s2: &'static [u8; 4] = br"text";
let s4: &'static [u8; 4] = br##"text"##;
assert_eq!(s1, s2);
assert_eq!(s1, s4);

カスタム型

  • メモリサイズにより以下に分類される。
    • 固定サイズ型 fixed-sized types
    • 動的サイズ型 Dynamically Sized Types (DSTs) - Nom/NoJ
    • ゼロサイズ型 Zero Sized Types (ZSTs) - Nom/NoJ
    • 空型 Empty Types - Nom/NoJ
  • v1.18より、#[repr]属性を持たないタプル・列挙型・構造体は、サイズを最小化するため自動的に(内部変数が)再配置される可能性がある。See Pull #40377.
  • enum Option<T> { None, Some(T), } のような列挙型については、None値をnullポインタで判別し、タグを付け足したりしない最適化が行われる。 - Nullable enum optimizations

構造体 Structs

Ref/1st/2nd/Grm/Ex./Jpn

  • .. でフィールドの一部コピーが簡略化できる。
    • 構造体パターンにおいて、構造体要素名の省略にも使える。
  • v1.17より、構造体の内部変数名と同一の変数については、コンストラクタにおいて添字を省略できる。See RFC 1682.
    • 構造体パターンにおいても同様。
#[derive(PartialEq, Debug)]
struct Point4d {
    x: i32, y: i32, z: i32, w: i32,
}

let origin = Point4d { x: 0, y: 0, z: 0, w: 0};
let point1 = Point4d { z: 1, x: 2, .. origin };

let (x, y, z, w) = (point1.x, point1.y, point1.z, point1.w);
let point2 = Point4d { x: x, y: y, z: z, w: w }; // old
let point3 = Point4d {    x,    y,    z,    w }; // RFC 1682

assert_eq!(point1, Point4d { x: 2, y: 0, z: 1, w: 0});
assert_eq!(point1, point2);
assert_eq!(point1, point3);

let point4 = Point4d { x: 20, y: 10, z: 11, w: 0};
//  構造体の分解
let Point4d { x: x2, z, ref y, .. } = point4; // ここで `z: z` とする必要は無い。
assert_eq!(x2, 20);
assert_eq!(*y, 10);
assert_eq!(z, 11);

タプル構造体 Tuple structs

  • タプルのように、丸括弧(,)で型を囲んで定義する構造体。
    • 要素に名前を持たない。
    • 基本的に1要素タプル構造体しか使われない。
      • 1要素タプル構造体は、他の型のラッパーを作るのに便利。See
    • タプルと同様に、0 から始まる自然数によるドット記法で内部値へアクセスできる。
struct Color(i32, i32, i32);
let c = Color(10, 20, 30);
assert_eq!(c.0, 10);
struct Inches(i32);

let length = Inches(10);

let Inches(integer_length) = length; // let を用いた内部値の取りだし。
println!("length is {} inches", integer_length);
  • v1.19よりタプル構造体は、自然数名の変数を持つ構造体のようにも構築することが出来るようになった。See RFC 1506.
#[derive(PartialEq, Debug)]
struct Point(u32, u32);

let x = Point { 0: 3, 1: 5 };
let y = Point { 1: 5, 0: 3 };

assert_eq!(x, y);

Unit-like 構造体 Unit-like structs

struct Electron;
let x = Electron;

列挙型 Enums

Ref/1st/2nd/Ex./Jpn

  • 内部に値を持てるので色々と便利に使える。「タグ付き共用体」
enum Message {
    Quit,                       // Unit-like 構造体変数
    ChangeColor(i32, i32, i32), // タプル構造体変数
    Move { x: i32, y: i32 },    // 構造体
    Write(String),              // タプル構造体変数
}

C-like 列挙型 C-like enums

Ex.

  • 内部値を持たない C-like 列挙型は、C言語のように数値型へと変換することが出来る。(See 1st/Jpn)
    • その値を明示的に与えることも出来る。
    • 数値型から列挙型への変換は一手間必要 (See サンプル)
#![allow(dead_code)]

 Number {
    Zero, One, Two,
}

#[derive(Debug, Copy, Clone)]
enum Color {
    Red   = 0xFF0000,
    Green = 0x00FF00,
    Blue  = 0x0000FF,
}

fn main() {
    use Number::*;
    println!("zero is {}", Zero as i32);
    println!("one is {}", One as i32);
    
    use Color::*;
    println!("{:?} is 0x{:06X}", Red, Red as i32);
    println!("{:?} is 0x{:06X}", Blue, Blue as i32);
    let color: Color = unsafe { std::mem::transmute(0x00ff00) };
    println!("0x{:06X} is {:?}", color as i32, color);
}

空列挙型

#![feature(never_type)]
#[allow(dead_code)]

enum Empty { }

fn test0(x: Empty) {
    match x {
        _ => unreachable!(), // warning: unreachable pattern
    }
}
fn test1(x: Empty) {
    match x {
        // no pattern is valid.
    }
}
fn test2(x: Option<Empty>) {
    match x {
        None => println!("Hello"),
        Some(_) => unimplemented!(), // warning: unreachable pattern (only if #![feature(never_type)])
    }
}

共用体 union

v1.19より、C言語にあるような共用体(タグ無し共用体)がサポートされた。See RFC 1444.

  • キーワードunionを用いて定義
  • 変数へのアクセスには unsafe が必要
    • 共用体はどの内部値で定義されたかを覚えていないので、異なる型へのアクセスが危険だから。
  • パターンマッチも可能。しかし繰り返すが、共用体は内部型を記憶しないので、let MyUnion { i } = a;のようなパターンマッチは常に成功することに留意されたし。
union MyUnion {
    f: f32,
    i: i32,
    x: [u8;4]
}
let a = MyUnion { f: 1.0 };
println!("a.f: {:.3}", unsafe { a.f });
println!("a.i: {}", unsafe { a.i });
println!("a.i: 0x{:08X}", unsafe { a.i });
println!("a.i: 0b{:032b}", unsafe { a.i });
println!("a.x: {:?}", unsafe { a.x });
unsafe { println!("a.x: 0b{:08b}_{:08b}_{:08b}_{:08b}", a.x[3], a.x[2], a.x[1], a.x[0]); };

unsafe {
    let MyUnion { f: fval } = a;
    let MyUnion { i } = a;
    println!("{:0.3} == {}", fval, i);
}
unsafe {
    match a {
        MyUnion { i: 0...10 } => println!("{}", a.i),
        MyUnion { f: fval } if fval == 1.0 => println!("{}", fval),
        MyUnion { f } => println!("{}", f),
    }
}

言語機能

型キャスト Casting Between Types

1st/Grm/Jpn

型強制 Type coercions

Ref/1st/Jpn

型強制とは(唯一)暗黙的に行われる以下の変換

  • mutableな参照(ポインタ)のimmutable化
    • &mut T -> &T
    • *mut T -> *const T
  • 参照からポインタへの変換
    • &T -> *const T
    • &mut T -> *mut T
    • ポインタから参照への変換には、&*等が必要(See 参照と生ポインタ
  • 参照外し演算子 * (Deref) (See Deref による型強制
    • 引数やメソッド呼び出しに際して、必要に応じて*は幾らでも自動的に挿入される(See Derefとメソッド呼び出し
      • 例えば構造体std::vec::Vec<T>は、これ自身はiter()メソッドを実装していないが、Deref<Target=[T]>を実装しているため、暗黙のDerefによる[T]への型変換経由で、iter()メソッドを呼び出すことが出来る。
    • この暗黙の Deref を用いて、「クラス継承」を再現できる。
fn test1(ptr: *const i32) { println!("{}", unsafe { *ptr }); }
fn test2(rf: &i32) { println!("{}", *rf); }

let rf: &i32 = &100;
let ptr: *const i32 = &100; // 暗黙変換
test1(rf);
//test2(ptr); // Error
test2(unsafe { &*ptr });

let v: Vec<i32> = vec!(1, 2, 3, 4);
let mut iter = v.iter(); // called [i32]::iter(& *v)
assert_eq!(iter.next(), &1);

as(安全キャスト)

1st/Jpn

  • キーワード as を用いて安全なキャストを行う。
  • 安全なキャストには以下の 3 種がある。
    • 明示的型強制 - 型強制で行われる型の変換を、明示的に行う。
    • 数値キャスト
      • 数値型同士の変換
      • C-likeな列挙型を整数型へ
      • 真偽値boolや文字型charを整数型へ
      • u8を文字型char
    • ポインタキャスト - 生ポインタと整数型の間の相互変換
  • Cとは違い、Rust では数値型が暗黙に適当に型変換されることは無いので、例えば1 + 1.0 のような式はエラーとなる。
    • 1 as f32 + 1.0 のように明示的に型を揃えるためのasキャストが必要。
    • 演算子の優先順位の表を見て分かるとおり、asは二項演算子の中では一番優先順位が高い
      • 故に、普通の数値演算において (1 as f32) + 1.0 のような括弧()は不要である。

transmute

1st/std/Jpn

  • 関数 std::mem::transmuteを用いて、unsafe な型変換が行える。
    • ビット単位の再解釈を行う。
      • そのため、型変換について型サイズの一致は確認する。
        • 違反するとエラーが出る。[#E0512]
    • 標準ライブラリのドキュメントに多くの例が挙げられているが、
      • ライフタイムの伸張といった、とんでもないことも行える。
use std::mem::transmute;

unsafe {

println!("{:?}", transmute::<_, [u8; 4]>(1.0f32));
println!("{:?}", transmute::<_, [u8; 8]>(1.0f64));
println!("{:?}", transmute::<_, [u8; 8]>(100i64));
let a: String = "Hello".to_string() + " World!";
println!("{:?}", transmute::<_, &'static str>(a.as_str()));

}

変換関数の例

所有権 Ownership

1st/Jpn/ExJ

Rust は所有権システムにより、1つのリソースは1つの変数にしかその所有権が持たれていないことを保証する。

  • 「変数」が「リソース(値、変数の中身)」について「所有権」を持っている。
    • リソースの所有権は他の変数への代入や関数適用時に、他の変数や関数(の引数)にムーブ move される。
    • リソースの所有権を失った変数はいわば抜け殻のようなもので、そのリソースへのアクセスはコンパイラエラーを引き起こす。(use of moved value #E0382)
      • mut変数なら、代入されることで再度リソースの所有権を保有することが出来る。
    • リソースが Copy トレイトを実装していたら、所有権のムーブではなく、リソースのコピーが行われ、元の変数の所有権は失われない。
#![allow(unused_variables)]

#[derive(Debug)]
struct Foo(i32);

let a = Foo(100); // `Foo(100)` というリソースの所有権を変数 `a` が持っている。
//  ここで1要素タプル構造体 `Foo(i32)` を用いたのは、普通の数値型だと `Copy` を実装しているので、
//  所有権がムーブしないため。

let mut b = a; // 変数 `a` の持つ `100` への所有権が変数 `b` へとムーブされた。
//  println!("{:?}", a); // <- 変数 `a` の持つリソースは既にムーブされているので、
//  コンパイルエラーが生じる。

let c = b;
b = c;  // 変数 `b` はmut変数なので、再度リソースを代入できる。

let d = b.0;    //  変数 `b` の要素 `b.0` の型は `i32` で `Copy` を実装しているため、
                //  新しい変数 `d, e` へとリソース `100` がコピーされる。
let e = b.0;    //  変数 `b` の所有権は失われない。

fn consume_foo(_foo: Foo) {}

consume_foo(b);
//  println!("{:?}", b);    //  既に関数 `consume_foo`(、正確にはその引数 `_foo`)へと
//  変数 `b` の所有権がムーブされているので、コンパイルエラーが生じる。
  • 所有権を持つ変数が失われたとき、リソースは解放される。
    • 「変数が失われたとき」とは、処理がリソースを所有している変数の宣言されたスコープを抜ける、変数に所有されないリソースがワイルドカード_に代入される、などのとき。
    • こうしてリソースが1回だけしか解放されないことが保証される。
    • タプルや構造体の要素は個別にムーブ、解放される。
#![allow(unused_variables,non_snake_case)]

#[derive(Debug)]
struct Foo(char);

impl Drop for Foo {
    fn drop(&mut self) { println!("Drop {:?}", self); }
}

println!("in #1");
{
    let a = Foo('a');
    let _ = Foo('_');   //  即値がワイルドカードにマッチされるとき、
                        //  このリソース`Foo('_')`を持つ変数がないので即ドロップ
    let b = Foo('b');
    let _ = b;          //  `Foo('b')`は変数`b`が持ったままなので、この行に意味は無い。
    println!("in #1.1");
    {
        let A = Foo('A');
        let B = Foo('B');
        let C = Foo('C');
        //  このブロックは、`{ let A = ...; { let B = ...; { let C = ...; }}}`
        //  と等価なので、`C, B, A` の順にドロップする。
    }
    println!("in #1.1");
    let c = Foo('c');
}
println!("out #1");

println!("in #2");
{
    let v012 = (Foo('0'), Foo('1'), Foo('2'));
    println!("in #2.1");
    {
        let (v0, _, v2) = v012;
        //  `v0`,`v2` に所有された `Foo('0'), Foo('2')` はここでドロップ
    }
    println!("out #2.1");
    //  println!("{:?}", v012.0);   //  `v012.0, v012.2` は既にムーブしているのでアクセスできない
    //  println!("{:?}", v012.2);
    println!("{:?}", v012.1);
    //  残りの `v012.1` の所有する `Foo('1')` はここでドロップ
}
println!("out #2");

println!("in #3");
{
    struct Bar { a: Foo, b: Foo, cd: (Foo, Foo) };
    //  `Bar`が`Drop`を実装していないから、以下のようなことが可能
    
    let bar = Bar { a: Foo('a'), b: Foo('b'), cd: (Foo('c'), Foo('d')) };
    println!("in #3.1");
    {
        let Bar { a, b: _, cd: (c, _) } = bar;
        //  `a`,`c` に所有された `Foo('a'), Foo('c')` はここでドロップ
    }
    println!("out #3.1");
    //  println!("{:?}", bar.a);    //  `bar.a, bar.cd.0` は既にムーブしているのでアクセスできない
    //  println!("{:?}", bar.cd.0);
    println!("{:?}", bar.b);
    println!("{:?}", bar.cd.1);
    //  残った `bar.b, bar.cd.1` の所有する `Foo('b'), Foo('d')` はここでドロップ
}
println!("out #3");
  • Dropトレイトを実装している型は、その部分要素をムーブすることができない。
    • コンパイルエラー#E0509を生じる。
    • 構造体分解も出来ない。
#[derive(Debug)] struct Foo(char);
#[derive(Debug)] struct Bar(Foo);

impl Drop for Foo { fn drop(&mut self) { println!("Drop {:?}", self); } }
impl Drop for Bar { fn drop(&mut self) { println!("Drop {:?}", self); } }

let foo = Foo('a');
let a = foo.0;  //  これはコピーだからOK
let bar = Bar(foo);
//  let foo = bar.0;    //  これはムーブになるからNG
//  let Bar(foo) = bar; //  構造体パターンによる分解も、要素のムーブになるからNG
  • Boxは言うまでもなくDropを実装した型であり、そのためBox<T>の成分取り出しはTの場合のようには行かない。
    • 単純に行うと、1つ成分を取り出すとその時点でBox値が consume され、2つ目の成分が取り出せない。(box test 1)
    • 値を一旦スタック上に移して、それから取り出す手も有る。(box test 2)
    • ブロックで囲むことで、box test 2 と同様に。(box test 3)
    • 自分自身を返すメソッドを実装する手もある。(box test 4)
    • ブロックで囲む box test 3 が一番単純な手だとは思うが、スマートじゃないような……
//  prelude
#![allow(unused_variables)]
struct Foo(i32);
struct Bar(Foo,Foo);

//  stack test
let bar = Bar(Foo(0), Foo(1));
let Bar(foo0, foo1) = bar;  //  OK

//  box test 1
let bar = Box::new(Bar(Foo(0), Foo(1)));
//  let Bar(foo0, foo1) = *bar; //  NG. `bar.0` が `foo0` へとムーブされた時点で `bar` は
                                //  consume され、`bar.1` へとアクセス出来なくなる。
let Bar(foo0, _) = *bar;    //  成分を1つかし取り出さない、これは OK

//  box test 2
let bar = Box::new(Bar(Foo(0), Foo(1)));
let Bar(foo0, foo1) = { let tmp = *bar; tmp };  // これは OK

//  box test 3
let bar = Box::new(Bar(Foo(0), Foo(1)));
let Bar(foo0, foo1) = { *bar }; //  box test 2 より、これも OK

//  box test 4
//  自分自身を返すメソッドを使っても OK
trait Ident: Sized {
    fn id(self) -> Self { self }
}
impl Ident for Bar {}

let bar = Box::new(Bar(Foo(0), Foo(1)));
let Bar(foo0, foo1) = bar.id(); //  OK

参照と借用 References and Borrowing

1st/2nd/Jpn

「借用」borrowing 機能により、リソースをコピーすることもその所有権をムーブすることもなく、リソースを用いることが出来る。

  • T の変数 x について、アンパサンド & を用いて &x と記述することで、型 T のリソースを借用する参照型(参照) &T を得る。
    • また、更にキーワード mut を用いることで、mutable な変数 x について、&mut x により、mutable な参照型 &mut T を得る。
    • あるいは ref / mut ref パターンを用いて参照型を得ることが出来る。
  • 1つの変数から同時に複数の immutable な参照を作ることが出来る。
  • 1つの mutable な変数からは、mutable な参照は同時に 1 つしか作ることが出来ない。
    • これを犯すと、コンパイルエラーが生じる。[#E0499]
  • immutable な参照と mutable な参照は、同時に作ることが出来ない。
    • これを犯すと、コンパイルエラーが生じる。[#E0502]
  • 借用されている変数は、参照が 1 つでも生きている間(=生存期間)、変更することが出来ない。
    • これを犯すと、コンパイルエラーが生じる。[#E0506]
    • このルールにより、immutable な参照の値が不変であることが保証される。
    • これにより、借用されているカスタム型について、その成分 element を変更することも出来ない。
  • 以上の性質により mutable な参照は所有権のムーブと似ている。
    • 異なるのは mutable な参照がスコープアウトして使えなくなった後、元の変数は通常通りに使用可能であると言うことである。
    • つまり、fn do_something(x: Foo) -> Foo のようにしてリソースの明示的な返却を行う(See 所有権を越えて)必要が無く、fn do_something(x: &mut Foo) で済むということである。
  • 変数ではなく、リテラルからも参照を作ることが出来る。
    • v1.21.0より、リテラルの参照は、'static なライフタイムを持つ。ライフタイムについては後述。
    • ただし何故かリテラルの &mut による mutable な参照は、ローカルスコープのライフタイムを持つ。(これはバグなんじゃなかろうか?)
#![allow(unused_variables)]

#[derive(Debug, PartialEq)]
struct Foo(i32);

let foo = Foo(123);

{   //  同時に無数の immutable な参照を作成できる.
    let r1: &Foo = &foo;
    let r2 = &foo;
    let ref r3 = foo; // ref パターンによる参照作成
    let r4: &Foo = r3;
    println!("{:?} -> {:?}, {:?}, {:?}, {:?}", foo, r1, r2, r3, r4);
}

let mut bar = Foo(123);

{   //  mutable な参照は同時に 1 つしか作成できない。
    {
        let r1 = &mut bar;
    //  let r2 = &mut bar; // <- Error
        r1.0 += 1;
    }
    {
        let ref mut r3 = bar; // ref mut パターンによる mutable な参照作成
        r3.0 += 1;
    }
    assert_eq!(bar, Foo(125));
}

{   //  immutable な参照と mutable な参照は、同時に作ることが出来ない。
    {
        let r1 = &mut bar;
        //  let r2 = &bar; // <- Error
    }
    {
        let r1 = &bar;
        //  let r2 = &mut bar; // <- Error
    }
}

{   //  借用されている変数は、変更することが出来ない。
    let r1 = &bar;
    //  bar.0 += 1; // <- Error
}

{   //  リテラルの参照は、`'static` のライフタイムを持つ.
    fn check_static(a: &'static Foo) { println!("{:?}", a); }
    
    let r1 = &Foo(1);
    check_static(r1);
    let ref r2 = Foo(2);
    check_static(r2);
    
    let r3 = &mut Foo(3);   // リテラルを `&mut` したものは `'static` なライフタイムを持たない
    //  check_static(r3);   // <- Error 
    let ref mut r4 = Foo(4);
    check_static(r4);
}
  • 参照は参照外し演算子 * により、一時的に参照から実体へと変化する。
    • しかし参照の参照外しにより、リソースの所有権をムーブすることは出来ない。
      • これを破ると、コンパイルエラーが生じる。[#E0507]
    • 一方で Copy を実装していると、リソースのコピーは可能。
  • immutable な参照は言うまでもなく、mutable な参照についても、リソースが元の変数へと返却される必要がある。
    • そのため、参照はリソースを consume する、もといリソースの破壊的な処理は行えない。
      • これを破ると、コンパイルエラーが生じる。[#E0507]
#![allow(unused_variables, dead_code)]

//#[derive(Debug, PartialEq)] 
struct Foo(i32);

impl Foo {
    fn not_consume(&self) {}
    fn consume(self) {}
}

let foo = Foo(100);
let val = 23i32;

{   //  参照からリソースをムーブすることは出来ない.
    let f = &foo;
    let g = &val;
    //  let f2 = *f;    // <- Error. ムーブ不可なのでエラー
    let g2 = *g;    // i32 はコピー可なので O.K.
}

{   //  参照からリソースをムーブすることは出来ない.
    let f = &foo;
    f.not_consume();    //  メソッド `consume(self)` は `self` を消費しないので可.
    //  f.consume();    // メソッド `consume(self)` は `self` を消費するので不可.
}
  • 参照型の実体はポインタである。
    • そのためキーワード as を用いたキャストにより、ゼロコストで生ポインタへと変換できる。
      • r / mut_r を型 T の immutable / mutable な参照 &T / &mut T として、
        • r as *const T, r_mut as *const Tconst ポインタ
        • r_mut as *mut Tmut ポインタ

カスタム型の借用

  • カスタム型について、その借用の管理は成分 element 単位で行われる。
#![allow(unused_variables)]

struct Foo(i32);
struct Bar { a: Foo, bc: (Foo, Foo), };

let bar = Bar { a: Foo(1), bc: (Foo(2), Foo(3)) };
{   //  カスタム型の成分をそれぞれ借用することが出来る.
    let b = &bar;
    let ba = &bar.a;
    let bb = &b.bc.0;
}

let mut bar2 = Bar { a: Foo(1), bc: (Foo(2), Foo(3)) };
{
    {   //  借用の管理は成分 element 単位で行われる
        let b_a = &bar2.a;
        let b_a2 = &bar2.a;
        let b_b = &mut bar2.bc.0;
        //  let b_b2 = &mut bar2.bc.0;  //  bar2.bc.0 は既に mutable 借用されているので、更に借用することは不可.
        let b_c = &bar2.bc.1;
        //  let b_c2 = &mut bar2.bc.1;  //  bar2.bc.1 は既に借用されているので、mutable な参照を作ることは不可.
    }
}

ライフタイム

  • 参照型の実体はポインタであるが、所有権システムと同様のシステムにより、ダングリング・ポインタ (dangling pointer) もといダングリング参照 (dangling references) の発生をコンパイラが防いでいる。
    • すなわち、参照の生存期間 (lifetime; ライフタイム) を、参照先の変数がリソースを所有している期間より常に短くコンパイラが制限している。
    • このことで、参照先の変数がリソースの所有権を失っているというようなこと、リソースが開放済みであるようなことが決して発生しない。

参照値の内部値の取りだし

let optional: Option<Box<i32>> = None;
check_optional(&optional);

let optional: Option<Box<i32>> = Some(Box::new(9000));
check_optional(&optional);

fn check_optional(optional: &Option<Box<i32>>) {
    match *optional {
        Some(ref p) => println!("have value {}", p),
        None => println!("have no value"),
    }
}

参照(借用)は内部値にも浸透するので、そのパターンマッチに際し ref が必要。

mem::replaceを使って、借用値をclone()すること無く書き換える

TL;DR: &mut変数から、値をムーブして得たいことがある。そのためには、目的の値を別の値で置換することで得る。

  • &mutで得ている変数やその変数の成分を他へムーブすることは出来ない。
  • mem::replacemem::swap は、ライフタイムや借用の制約がある Rust プログラミングにおいて多用する、共に重要な関数である。
  • 以下は A.x == 0 のときに B へと変換するコード。
    • ナイーブな処理では、借用値の中身をムーブ出来ないとエラーが出る。
    • clone()する処理では上手く行くが、クローン分無駄なコストがかさむ。
    • そこで mem::replace して目標を空文字列と入れ替えて取り出すと、上手く行く。
      • 空文字列 String::new() だけではヒープ確保は行われないので、余計なコストを増やさずに済む。
    • なお、*eへの代入がif let式の外側に出ているのは、if let式の内部ではeが借用されているため、更新できないから。
    • ただしこの例は、fn a_to_b(e: MyEnum)と引数をconsumeする関数の方が妥当かもしれない。
use std::mem;

enum MyEnum {
    A { name: String, x: u8 },
    B { name: String }
}

/*
fn a_to_b_naive(e: &mut MyEnum) {
    *e = if let MyEnum::A { ref mut name, x: 0 } = *e {
        MyEnum::B { name: *name }
//                        ^^^^^ cannot move out of borrowed content
    } else { return }
}
*/

fn a_to_b_clone(e: &mut MyEnum) {
    *e = if let MyEnum::A { ref mut name, x: 0 } = *e {
        MyEnum::B { name: name.clone() }
    } else { return }
}

fn a_to_b(e: &mut MyEnum) {
    *e = if let MyEnum::A { ref mut name, x: 0 } = *e {
        MyEnum::B { name: mem::replace(name, String::new()) }
    } else { return }
}

型注釈なしの変数への代入

型注釈の無い変数への代入は、元の型が参照型であっても、新しい変数が参照先のリソースの所有権を奪い取る(ムーブさせる)ので注意。

See Issue #35919: Adding type annotation equivalent to existing type changes code semantics

  • 次のコードは問題なくコンパイルが通る。
    • let x: &mut _ = y; の行は let x = y as &mut _; と変えても等価。
fn main()
{
    let y = &mut 2i32;
    {
        let x: &mut _ = y;
        *x = 3;
    }
    println!("{}", y);
}
  • しかし型注釈を取り除いた次のコードではコンパイルエラーが生じる。
    • println!();の行で use of moved value: y.
    • これはlet x = y;の行で、xyの持つ所有権がムーブされるためである。
fn main()
{
    let y = &mut 2i32;
    {
        let x = y;
        *x = 3;
    }
    println!("{}", y);
}
  • 2つのコードでxの型は同一であるのに型注釈の有無で挙動が変わるのは不合理なので、何時か修正されるかも知れない。

実装 impl

implキーワードを用いた実装には、以下の2種類がある。

  • 固有実装 - impl AType { ... } の形式。型に関数を関連付ける。
  • トレイト実装 - impl ATrait for AType { ... } の形式。型にトレイトを実装させる。

固有実装 inherent implementation

  • 関連付ける関数(メソッド)は、以下の2種類に分けられる。
    • クラスメソッド(関連関数)
    • インスタンスメソッド
  • 固有実装が書けるのは、型の定義されているクレート内部のみ。[#E0116]
    • よって、例えば Vec<T>std クレートで定義されているため、ユーザーが勝手にメソッドを追加することは出来ない。
    • 故に、外部クレートの型について固有実装を書きたいときは、1要素タプル構造体などのラッパーを自分で書く必要がある。
メソッド構文 Method Syntax

1st/Jpn

  • キーワードselfを用いて、インスタンスメソッド(のようなもの)を定義できる
    • 第1引数が &self, &mut self, self のいずれかである
    • インスタンスメソッドは、インスタンスに対しドット.を用いたメソッド呼び出しが行える。
      • 例えばインスタンスメソッド fn method(&self, val: i32) は、その型Fooのインスタンスfoo に対し、foo.method(10) で呼び出しが行える。
    • ドットを用いない呼び出しも行える。
      • 先の例においては、foo.method(10) == Foo::method(&foo, 10)
    • 定義において
      • fn method(&self) { ... } == fn method(self: &Self) { ... }
      • fn method(&mut self) { ... } == fn method(self: &mut Self) { ... }
      • fn method(self) { ... } == fn method(self: Self) { ... }
        • 前者は後者の糖衣構文
    • fn method(self)メソッドにおいては、selfは消費 (consume) されてしまうことに留意。
      • というか self を消費(破損)するようなメソッドを、これで記述する。
      • 一方fn method(&self),..(&mut sulf)メソッドにおいては、メソッド呼び出し後にselfは借用から戻ってくる。
  • それ以外は、クラスメソッド(のようなもの)である
    • Self::new() は、よく使われるクラスメソッドの代表例
#[derive(Debug)]
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}
impl Circle {
    fn new(x: f64, y: f64, radius: f64) -> Self {
        Circle { x: x, y: y, radius: radius, }
    }
}
impl Circle {
    fn reference(&self) {
       println!("taking self by reference!");
    }
    fn mutable_reference(&mut self) {
       println!("taking self by mutable reference!");
    }
    fn takes_ownership(self) {
       println!("taking ownership of self!");
    }
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

let c = Circle::new(0.0, 0.0, 1.0);
c.reference();
println!("{:?}'s area = {}", c, c.area());
println!("{:?}'s area = {}", c, Circle::area(&c));
c.takes_ownership(); // ここで c が消費される
// c.reference(); // よってもう c は使えない

トレイト実装 trait implementation

  • トレイトの実装においては、関数(メソッド)を実装するだけでなく、以下の関連付けも行う。
関連型

1st/Jpn

  • 型やトレイトの定義において、他の型を関連付けることで、メソッドの引数や戻り値の型の扱いが簡単になる。

関連定数 Associated constants

Old/Jpn

  • v1.20より、型に定数を関連付けられるようになった。
#[derive(Debug)]
struct Foo;

impl Foo {
    const ID: i32 = 9;
    const FOO: Self = Foo;
}

trait Bar {
    const ID2: i32;
}

impl Bar for Foo {
    const ID2: i32 = Foo::ID * 10;
}

println!("{}", Foo::ID);
println!("{:?}", Foo::FOO);
println!("{}", Foo::ID2);

クロージャ Closures

1st/Ex./Jpn

  • クロージャはバーティカルバー | で引数を囲むことで記述する
  • 引数、戻り値の型名は省略も明示も可能
let plus_one = |x| x + 1;
let plus_two = |x: i32| { x + 2 };
let plus_three = |x: i32| -> i32 { x + 3 };

assert_eq!(3, plus_one(2));
assert_eq!(3, plus_two(1));
assert_eq!(3, plus_three(0));
//  let plus_4: Fn(i32) -> i32 = |x| x + 4; Error
//      ^^^^^^ `std::ops::Fn(i32) -> i32` does not have a constant size known at compile-time

let plus_4_box: Box<Fn(i32) -> i32> = Box::new(|x| x + 4); // OK.

assert_eq!(3, plus_4_box(-1));

moveクロージャ move closures

1st/Jpn

  • moveキーワードをクロージャ構文の前に置くことで、クロージャに環境の取得を強制する。
  • クロージャを引数に取るには、型にトレイト std::ops::{Fn, FnMut, FnOnce} を用いる。
    • クロージャを引数に取る関数に、関数ポインタを与えることも出来る。
let mut counter = {
    let mut tmp = 0i32;
    move |step: i32| { tmp += step; tmp } // move が無いとライフタイムエラー
};

fn call_with_2<F>(f: &mut F) -> i32
    where F: FnMut(i32) -> i32 {
    f(2)
}

assert_eq!(counter(1), 1);
assert_eq!(call_with_2(&mut counter), 1+2);
assert_eq!(counter(3), 1+2+3);

fn triple_func(v: i32) -> i32 { v * 3 }

assert_eq!(call_with_2(&mut triple_func), 6);

マクロ Macros

1st/Jpn

  • foo!()のように、括弧の前・識別子の後に!が付いてるのがマクロ呼び出し。
    • マクロ呼び出しには、丸括弧()の他に波括弧{}・角括弧[]を用いることが出来る。
      • foo! {1, 2}, foo!(1, 2); - (foo!がユニット型を返すとき)これら2つはほぼ等価。
      • 波括弧{}の呼び出しでは、マクロの値がユニット()であるとき、区切りのセミコロン;が不要。
        • アイテム的に働くマクロのとき、この波括弧{}呼び出しを使うと分かり易いだろう。
      • test_macro![]; - 何故か角括弧[]でも呼び出せる。
macro_rules! test { () => ( 10 ); }
{
    test!(); 
    test!{}; // <- need ';'
    test![];
}

macro_rules! test { () => ( ; ); }
{
    test!();
    test!{} // <- don't need ';'
    test![];
}
  • Rust には可変長引数関数が無いので、それらの機能はマクロにより提供される。例:println!, vec!
  • マクロの引数において、各括弧(丸括弧()・角カッコ[]・波括弧{})の対応は完全に整合していなければならない。
    • この対応により、コンパイラはマクロの展開を後回しすることが出来る。

カスタムマクロ

Ref/1st/Grm/Ex./Jpn/ExJ

  • 識別子 macro_rules! を用いて、カスタムマクロを定義できる。
  • 引数に対してパターンマッチを行い、新しい構文木を作る。
    • マクロ引数に対する独自のパターンは、マッチャー matcher と呼ぶ。
  • マッチャーにおける変数は、頭にドル記号$が、尻にはフラグメント指定子 (fragment specifier) が付く。
    • フラグメント指定子は、構文木における属性を意味する。
指定子 意味 説明
ident 識別子 x, foo 識別子1つ
path パス std::io モジュールや名前空間を表すパス1つ
expr 1 + 2, foo(x) 値や関数の組み合わせて作られた式1つ
ty i32, &str, Foo 型1つ
pat パターン Some(x), (x, y), _, ref x match式やlet文で使われるパターン1つ
stmt let x = 10.0, use std::*; let文やアイテムとかの文1つ
block ブロック {}, { println!("Hello"); } 波括弧{}で括られたブロック1つ
item アイテム fn foo() {} fnアイテム(関数定義文)とかのアイテム1つ
meta メタアイテム derive(Copy), cfg(foo = "Foo") 属性付与文#[〜]中にあるアイテム1つ
tt トークン木 10, { println!("!?"); } トークン木1つ
  • マッチャーにおいては、正規表現と同様に、$(...)*/$(...)+で0回以上/1回以上のくり返しを表す。
    • ここで*/+の前に,;などの文字を置くと、適当にデリミタとして働く。
      • このデリミタの文字は、Rust 文法の整合性から、修飾する識別子により制限を受ける。
        • $pa:ident に対しては、,,;,|,||,=>,=,-,~,!,@,#,%,^,<,>,<<,>>,? etc.
        • $ex:expr に対しては ,; など。
        • $st:stmt に対しては ,; など。
        • $pa:pat に対しては ,| など。
        • $it:item に対しては ,; など。
        • $ty:ty に対しては ,,;,:,|, etc.
        • $bl:block に対しては、identと同様に沢山
        • $tt:tt に対しては、identと同様に沢山
        • $me:meta に対しては、identと同様に沢山
macro_rules! twice {
    ( $( $x:stmt ),* ) => {
        $(
            $x;
        )*
        $(
            $x;
        )*
    };
}

twice!(print!("1 "));
twice!(print!("2 "), print!("3 "));
// result: 1 1 2 3 2 3
  • 変数に与えるフラグメント指定子により、変数にマッチさせた引数は同一でも、生成可能な式に違いが出る。
    • 例えば次のマクロは、identtt以外の指定子ではうまく働かない。
macro_rules! get_max (($x:ident) => (std::$x::MAX));
println!("{}", get_max!(u8));
  • cargo rustc -- -Z unstable-options --pretty=expanded でマクロの展開結果が確認できる。
    • Nightly チャネルのみ可。
  • 以下、フラグメント指定子によるマッチングの例。
expr 識別子
macro_rules! match_expr {
    ($e:expr) => ( println!("{} is a expr.", stringify!($e)) );
    ($($e:expr)+) => ( println!("{} are exprs.", stringify!($($e)+)) );
}
match_expr!("a");
match_expr!(1 + 2);
match_expr!(foo(10));
match_expr!(if a { 0 } else { 1 });
ty 識別子
macro_rules! match_ty {
    ($t:ty) => ( println!("{} is a ty.", stringify!($t)) );
    ($($t:ty)+) => ( println!("{} are tys.", stringify!($($t)+)) );
}
match_ty!(Foo); //  unknown type
match_ty!([i32]); // slice
match_ty!([i32; 4]); // array
match_ty!(*const Foo); // pointer
match_ty!(&'a Foo); // reference
match_ty!(fn(i32) -> &str); // fn
match_ty!(Fn(i32) -> &str);
match_ty!(!); // nevar
match_ty!((i32,)); // tuple
match_ty!((i32,float,&'static str));
match_ty!(my::Foo::Bar); // path
match_ty!(<Vec<T> as SomeTrait>::SomeType);
match_ty!(Clone + Copy); // trait object
match_ty!(Clone + Copy + 'a + 'b);
match_ty!(impl ATrait); // impl trait
match_ty!((i32)); // parened
match_ty!(self);
match_ty!(Self);
match_ty!(Foo<i32,Bar>);
match_ty!(());
match_ty!((i32,));
match_ty!(Bar(i32));
pat 識別子
macro_rules! match_pat {
    ($p:pat) => ( println!("{} is a pat.", stringify!($p)) );
    ($($p:pat)+) => ( println!("{} are pats.", stringify!($($p)+)) );
}
match_pat!(_);
match_pat!(Some(x));
match_pat!(Foo { val: ref x} );
match_pat!(mut x);
match_pat!(3...5);
match_pat!(x @ 3...5);
match_pat!(a);
// match_pat!(x if x > 10); // not pat
// match_pat!(1 | 3); // not pat
stmt 識別子
  • stmt は文にマッチする。
  • stmt は値(式)にもマッチする。
    • しかし式を$s:stmtにマッチさせても、そのままでは式あるべきところに$sは置けない。
      • foo($s) はマクロの引数を適当に与えてもエラーとなる。
      • foo({$s}) の様に、ブロックで囲むと良い。
macro_rules! match_stmt {
    ($e:stmt) => ( println!("{} is a stmt.", stringify!($e)) );
    ($($e:stmt)+) => ( println!("{} are stmts.", stringify!($($e)+)) );
}
// 以下、引数のセミコロンの有無に注目
match_stmt!("a");
match_stmt!(1 + 2);
match_stmt!(foo(10));
match_stmt!(let a = 10);
match_stmt!(use std::*;);
match_stmt!(const A: i32 = 10;);
match_stmt!(static A: i32 = 10;);
match_stmt!(fn f() {});
match_stmt!(struct foo(););
match_stmt!(struct bar {} );
match_stmt!(type a = b;);
match_stmt!(macro_rules! test {} );
match_stmt!(test!{} test!{} ); // stats
match_stmt!(let a let b); // let a; let b; are stmts. WHY?
macro_rules! myvec{
    ( $( $x:stmt );* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                // temp_vec.push($x); // ERROR
                temp_vec.push({$x});
            )*
            temp_vec
        }
    };
}

println!("{:?}", myvec!(10)); // [10]
println!("{:?}", myvec!(10; 20; 30)); // [10, 20, 30]
block 識別子
macro_rules! match_block {
    ($b:block) => ( println!("{} is a block.", stringify!($b)) );
    ($($b:block)+) => ( println!("{} are blocks.", stringify!($($b)+)) );
}

match_block!( {} );
match_block!({a + b;});
match_block!({} {});
tt 識別子
  • トークン木 (token trees) がどういう物か分かりにくいので、引数のトークン木の数と、単一トークン木で分割した物を表示するマクロを書いてみた。
    • 結果、リテラルや識別子1つ、キーワードや記号1つ、ブロック1つ等が単一トークン木であることが分かる。
macro_rules! count_tt {
    () => ( 0 );
    ($t0:tt $($t:tt)*) => ( 1 + count_tt!($($t)*) );
}

macro_rules! split_tt {
    ($t:tt) => ( stringify!($t) );
    ($t:tt $($ts:tt)+) => ( concat!(stringify!($t), " | ", split_tt!($($ts)+)) );
}

macro_rules! info_tt {
    () => ( println!("0 tt") );
    ($t:tt) => ( println!("1 tt  = {}", stringify!($t)) );
    ($($t:tt)+) => ( println!("{} tts = {}", count_tt!($($t)+), split_tt!($($t)+)) );
}

info_tt!();
info_tt!(());
info_tt!(x);
info_tt!("Hello");
info_tt!(1 + 2);
info_tt!( (1 + 2) );
info_tt!(foo(x););
info_tt!( let a: i32 = 10; );
info_tt!( fn foo { test(); } );
info_tt!( struct foo(i32); );
info_tt!( struct bar {} );
info_tt!( { println!(Hello); } );

結果は

0 tt
1 tt  = (  )
1 tt  = x
1 tt  = "Hello"
3 tts = 1 | + | 2
1 tt  = ( 1 + 2 )
3 tts = foo | ( x ) | ;
7 tts = let | a | : | i32 | = | 10 | ;
3 tts = fn | foo | { test (  ) ; }
4 tts = struct | foo | ( i32 ) | ;
3 tts = struct | bar | {  }
1 tt  = { println ! ( Hello ) ; }

手続きマクロ Procedural Macros

Ref/1st/Jpn

  • 上記のマッチャーによるマクロとは異なり、コンパイル時に呼び出され、構文木を直接変換する関数によるマクロ機能が存在する。
    • これを手続きマクロ (Procedural Macros)、またはコンパイラプラグインと呼ぶ。
    • パターンマッチによるマクロより非常に柔軟なマクロが記述できる。
      • しかし扱いは格段に難しく、デバッグ困難なバグの温床ともなりうる。

std::* の主要なマクロ

  • 以下の標準マクロのソースは大体 src/libcore/ 以下にあるが、特殊な処理のマクロはコンパイラ組み込みであることが多い。
特定型処理
  • vec! - Vec<T>生成に使われる。
プリント用
デバッグ用
  • assert!, assert_eq! - C言語の assert() 的な。アサーション失敗時に表示されるメッセージを、println!の形式で与えることも出来る。
    • assert!(a + b == 30);
    • assert!(a + b == 30, "a = {}, b = {}", a, b);
    • assert_eq!(a + b, 30);
    • assert_eq!(a + b, 30, "a = {}, b = {}", a, b);
    • assert_eq! に与える変数は、std::fmt::Debugトレイトの実装が要求されることがあるので注意。
  • debug_assert!, debug_assert_eq! - 上と似てるが、最適化時に除去される。
エラー処理
  • try! - Result型からOkの内部値を取り出す。もしErrならpanicを起こす。
  • panic! - panicを起こし処理を停止する。値やフォーマットメッセージを与えることも可能。See src.
    • panic!();
    • panic!("this is a terrible mistake!");
    • panic!(4); // panic with the value of 4 to be collected elsewhere
    • panic!("1 + 2 = {}", 1 + 2);
特殊宣言
  • unreachable! - 到達しないことを宣言する。到達するとpanicを起こす。
    • 最適化のために使われるのは、std::mem::unreachable関数の方。
    • 値やフォーマットメッセージを与えることも可能。See src
      • unreachable!();
      • unreachable!("Unreachable!!!!");
  • unimplemented! - 現在未実装であることを宣言する。
    • 値やフォーマットメッセージを与えることも可能。See src
コンパイル時処理
  • file!, module_path!, line!, column! - ファイル名、モジュールパス、行、カラムを取得する。
    • file!(), module_path!() - 戻り値の型は &'static str
    • line!(), column!() - u32
  • stringify! - 引数を評価せずにそのまま &'static str に変換する。
    • assert_eq!(stringify!(1 + 1), "1 + 1");
  • concat! - 引数を結合して &'static str に変換する
    • assert_eq!(concat!("test", 10, 'b', true), "test10btrue");
  • concat_idents! - [Unstable] 識別子 (identifier) を結合する。C言語の foo##bar みたいな?
  • include!, include_bytes!, include_str! - コンパイル時に外部ファイルを読み込む。
  • env!, option_env! - 環境変数を取得
  • cfg! - 構成フラグを取得して真偽値を返す。See 1st/Jpn
    • cgf!(debug_assertions) - debug_assert!が呼ばれるときに true
#![feature(core_intrinsics)]

fn get_type_of<T>(_: &T) -> &'static str {
    unsafe { std::intrinsics::type_name::<T>() }
}

macro_rules! print_ident {
    ($v:ident) => (println!("{}: {} = {:?}", stringify!($v), get_type_of(&$v), $v));
}

fn main() {
    if cfg!(debug_assertions) {
        println!("Here is called when `debug_assertions` flag is on.");
    }

    let file = file!();
    let mod_path = module_path!();
    let line = line!();
    let column = column!();
    
    print_ident!(file);     // file: &str = "src/main.rs"
    print_ident!(mod_path); // mod_path: &str = "playground"
    print_ident!(line);     // line: u32 = 15
    print_ident!(column);   // column: u32 = 17
}
マルチスレッド関連

モジュールシステム

  • mod.rs/lib.rs(/main.rs) の名のファイルは、同じ階層における他の.rsファイル(モジュール)の公開情報を記述する特別なファイル。

mod.rs/lib.rs

  • mod.rs ファイルには以下のような記述が並ぶ。
    • pub修飾子は、当然 pub(super) のように可視範囲を指定できる。See 可視性.
    • mod mod_name
    • pub mod mod_name
    • use path
    • pub use path
      • pub use self::path::item - 等のリネームはよく使う。ファイル分割によって長くなったパスを省略するため。
    • mod { items }
  • 以下は例。
foo/mod.rs
mod a;
pub mod b;
mod c;
mod d;
mod bar;

// ここの `self` は階層 `foo` を意味する。
use self::c::C; // fooモジュール内において「foo::c::C」の代わりに「foo::C」と書けるようになる。
pub use self::d::D; // 「foo::d::D」の代わりに「foo::D」と。
pub use self::bar::x::X; // 「foo::bar::x::X」の代わりに「foo::X」と。
pub use self::bar::y::Y as BarY; // 「foo::bar::y::Y」の代わりに「foo::BarY」と。

pub mod baz {
    pub use self::bar::z::Z; // 「foo::bar::z::Z」の代わりに「foo::baz::Z」と。
}
foo/bar/mod.rs
pub mod x;
foo/bar/x.rs
use super::a::A; // ここの `super` は階層 `foo::bar` を意味する。

struct X(A);
main.rs
mod foo;

//use self::foo::a; // <- private Module
use self::foo::b::*;
use self::foo::D; // foo::d::D; も可能

可視性 visibility

Ex./Refβ

  • キーワードpubを用いた修飾で、可視性を記述することが出来る。
  • pub(〜)の形式で、公開範囲を限定できる。
    • pub(in mod_name) - キーワードinを用いて、指定モジュール内で可視
    • pub(crate_name) - 指定クレート内で可視
    • pub(self) - 修飾されるアイテムが有るモジュール内で可視
    • pub(super) - 修飾されるアイテムが有るモジュールの上位モジュール内で可視

属性 Attributes

Ref/1st

次のように、#![〜], #[〜]で様々な属性を設定できる。

  • #![〜] - 記された箇所を囲むアイテムに適用される。up↑的な。(inner attribute)
    • 冒頭(ファイル冒頭、関数本体部分の出だし、etc)に置く必要がある。
  • #[〜] - 記された箇所に続くアイテムに適用される。next→的な。
// モジュール or クレートに作用
#![crate_type = "lib"]

// 続く関数 `test_foo` に作用
#[test]
fn test_foo() {
    /* ... */
}

// 続くモジュール `bar` に作用
#[cfg(target_os="linux")]
mod bar {
    /* ... */
}

// 続く型 `int8_t` に作用
#[allow(non_camel_case_types)]
type int8_t = i8;

lintチェック属性 Lint check attributes

lintチェック C について、

  • allow(C) - C 違反を許可する(警告を出さない)
  • deny(C) - C 違反についてエラーを出す
  • forbid(C) - deny(C) とほぼ同様だが、lintレベルの変更下で違いが出る
  • warn(C) - C 違反について警告を出す

主なlintチェックとして以下がある。

  • non_snake_case
  • dead_code
  • unused_variables
  • ...

#[inline]

Ref

インライン展開についてコンパイラーにヒントを与える。Cのinlineキーワード的な

  • #[inline]
  • #[inline(always)] - 「常に」インライン展開するべきと要請する。
    • コンパイラに任せた方が性能向上が見込めるので、使用には要注意。
  • #[inline(never)] - 「決して」インライン展開するべきでないと要請する

#[derive]

Ref/Ex.

#[derive(Debug, Clone, Copy)]
struct Foo {
    id: i32,
    val: f32,
    ch: char,
}

let foo1 = Foo { id: 10, val: 1.23, ch: '♡'};
let foo2 = foo1; // copy foo1
println!("{:?}", foo1); // Foo { id: 10, val: 1.23, ch: '♡' }
println!("{:?}", foo2); // Foo { id: 10, val: 1.23, ch: '♡' }

#[feature]

  • 不安定な非標準機能の使用を可能とする。
  • 不安定ゆえ Stable, Beta チャネルにおいてはエラー ([E0554]) となり、Nightly チャネルのみ使用可能。
    • $ rustup default nightly, $ rustup run nightly cargo run 等が必要
  • 例:サンプル「型名表示

#[cfg] Conditional compilation

Ref/1st/Jpn

cfg属性で条件付きコンパイルが可能。その条件としては、

  • ターゲットOS/アーキテクチャ/OSファミリー/プラットフォーム/エンディアン/ポインタサイズ/ベンダー/……
  • テスト
  • デバッグ・アサーション
  • などなど、リファレンスに色々書いてある

またany,all,notなどの補助関数も条件に対して用いることが出来る。

// The function is only included in the build when compiling for macOS
#[cfg(target_os = "macos")]
fn macos_only() { /* ... */ }

// This function is only included when either foo or bar is defined
#[cfg(any(foo, bar))]
fn needs_foo_or_bar() { /* ... */ }

// This function is only included when compiling for a unixish OS with a 32-bit
// architecture
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() { /* ... */ }

// This function is only included when foo is not defined
#[cfg(not(foo))]
fn needs_not_foo() { /* ... */ }

test属性 The test attribute

関数に#[test]test属性を与えることで、テスト関数であることを指定する。テストは$ cargo testコマンドにより行われる。
関連アイテム

  • should_panic属性 - 失敗するべきテストに与えられる属性
    • expectedパラメータ - 失敗した際に与えられたメッセージに含まれるべき文字列を指定するパラメータ
  • ignore属性 - デフォルトでは省略されるテストに与えられる属性
    • 1st/Jpn
    • ランニングコストが高いテスト等に使う
    • $ cargo test -- --ignoredの様に、テスト実行ファイルに--ignoredのオプションを与えることで実行される。
  • #[cfg(test)] - モジュールにこの条件付きコンパイル属性を与えることで、テスト時のみにテスト部分がコンパイルされ、コンパイル時間・バイナリサイズの節約になる。
#[cfg(test)]
mod a_test {
    #[test]
    fn it_works() {
        assert_eq!(1 + 1, 2);
    }
    #[test]
    #[should_panic(expected = "assertion failed")]
    fn it_failed() {
        assert_eq!(1 + 1, 0);
    }
    #[test]
    #[ignore]
    fn long_time_needed() {
        for i in 0i64.. {
            assert!(i >= 0);
        }
    }
}

/* cargo test の結果
running 3 tests
test a_test::long_time_needed ... ignored
test a_test::it_works ... ok
test a_test::it_failed ... ok

test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
*/

もしテストに失敗したら、test result: ok.test result: FAILED. となる。

モジュール

以下

  • イテレータ型 - std::iter::Iterator トレイトを実装した構造体。
  • リーダー型 - std::io::Read トレイトを実装した構造体。
  • ライター型 - std::io::Write トレイトを実装した構造体。
  • [Buf]リーダー型 - std::io::Read or std::io::BufRead トレイトを実装した構造体。

を意味することにする。他の「○○型」は、適当に『○○を意味する/表す構造体』程度の意味。

std::prelude

デフォで読み込まれている(useされている)アイテム群。

バージョン1 (v1) においては以下の要素がデフォで使える。

std::convert

型変換のためのトレイト群。

トレイト

std::ops

演算子オーバーロード。以下のトレイトを実装することでユーザ定義型に演算子操作を可能とする。

構造体 Structs

  • Range - assert_eq!((3..5), std::ops::Range{ start: 3, end: 5 });
  • RangeFrom - assert_eq!((2..), std::ops::RangeFrom{ start: 2 });
  • RangeTo - assert_eq!((..5), std::ops::RangeTo{ end: 5 });
  • RangeFull - assert_eq!((..), std::ops::RangeFull);

トレイト

以下の演算子オーバーロード用のトレイトがある。比較演算子についてはstd::cmpを参照。

単項算術・論理演算子
  • Neg - 単項演算子-
  • Not - 単項演算子!
二項算術演算子
  • Add - 二項演算子+
  • Sub - 二項演算子-
  • Mul - 二項演算子*
  • Div - 二項演算子/
  • Rem - 二項演算子%
二項ビット演算子
  • BitOr - 二項演算子|
  • BitAnd - 二項演算子&
  • BitXor - 二項演算子^
  • Shl - 二項演算子<<
  • Shr - 二項演算子>>
算術代入演算子
ビット代入演算子
特殊演算子
関数呼び出し演算子

クロージャを受けるトレイトにもなる。

  • Fn - 関数呼び出し演算子() call(...)
  • FnMut - 同上. call_mut(...)
  • FnOnce - 同上. call_once(...)

std::option

optionモジュールはかなり頻繁に使われる基本モジュールの一つ。
あるか、ないか (Some(x) or None) を表す Option<T> がその主要なクラス。
モダンな言語でよくある Nullable.

if let Some(ref x) = xxx {
    x.do_something();
} else {
    unreachable!();
}

のようなコードは、

let x = xxx.as_ref().unwrap();
x.do_something();

で代用できることに留意。

列挙型 Enums

Option<T>のメソッド

判定メソッド
  • fn is_some(&self) -> bool - Some値ならtrue
  • fn is_none(&self) -> bool - None値ならtrue
変換メソッド
  • fn as_ref(&self) -> Option<&T>
    • Some(x) -> Some(&x)
    • None -> None
  • fn as_mut(&mut self) -> Option<&mut T>
    • Some(x) -> Some(&mut x)
    • None -> None
  • fn map<U, F>(self, f: F) -> Option<U> where F: FnOnce(T) -> U
    • Some(x) -> Some(f(x))
    • None -> None
  • fn map_or<U, F>(self, def: U, f: F) -> U where F: FnOnce(T) -> U
    • Some(x) -> f(x)
    • None -> def
  • fn map_or_else<U, D, F>(self, def: D, f: F) -> U where D: FnOnce() -> U, F: FnOnce(T) -> U
    • Some(x) -> f(x)
    • None -> def()
assert_eq!(Some(&"Hello"), Some("Hello").as_ref());
assert_eq!(None, (None as Option<String>).as_ref());

assert_eq!(Some(&mut "Hello"), Some("Hello").as_mut());
assert_eq!(None, (None as Option<String>).as_mut());

assert_eq!(Some(5), Some("Hello").map(|s| s.len()));
assert_eq!(None, (None as Option<String>).map(|s| s.len()));

assert_eq!(5, Some("Hello").map_or(10, |s| s.len()));
assert_eq!(10, (None as Option<String>).map_or(10, |s| s.len()));

assert_eq!(5, Some("Hello").map_or_else(|| 10, |s| s.len()));
assert_eq!(10, (None as Option<String>).map_or_else(|| 10, |s| s.len()));
Result型変換メソッド
  • fn ok_or<E>(self, err: E) -> Result<T, E>
    • Some(x) -> Ok(x)
    • None -> Err(err)
  • fn ok_or_else<E, F>(self, err: F) -> Result<T, E> where F: FnOnce() -> E
    • Some(x) -> Ok(x)
    • None -> Err(err())
取り出しメソッド
  • fn unwrap(self) -> T
    • Some(x) -> x
    • None -> panic
  • fn expect(self, msg: &str) -> T
    • Some(x) -> x
    • None -> panic with メッセージmsg
  • fn unwrap_or(self, def: T) -> T
    • Some(x) -> x
    • None -> def
  • fn unwrap_or_else<F>(self, f: F) -> T where F: FnOnce() -> T
    • Some(x) -> x
    • None -> f()
  • fn unwrap_or_default(self: Option<T>) -> T where T: Default
    • Some(x) -> x
    • None -> T::default()
イテレータメソッド
  • fn iter(&self) -> Iter<T>
  • fn iter_mut(&mut self) -> IterMut<T>
let a = Some(5);
let mut iter = a.iter();
assert_eq!(Some(&5), iter.next());
assert_eq!(None, iter.next());

let b: Option<i32> = None;
assert_eq!(None, b.iter().next());

let mut c = Some(5);
if let Some(v) = c.iter_mut().next() {
    assert_eq!(v, &mut 5);
    *v = 10;
}
assert_eq!(Some(10), c);
論理メソッド
  • fn and<U>(self, optb: Option<U>) -> Option<U>
    • self,optb 共に Some -> optb
    • otherwise -> None
  • fn and_then<U, F>(self, f: F) -> Option<U> where F: FnOnce(T) -> Option<U> - map_or/_elseと似ている
    • Some(x) -> f(x)
    • None -> None
  • fn or(self, optb: Option<T>) -> Option<T>
    • Some(x) -> Some(x)
    • None -> optb
  • fn or_else<F>(self, f: F) -> Option<T> where F: FnOnce() -> Option<T>
    • Some(x) -> Some(x)
    • None -> f()
assert_eq!(Some(5), Some(3).and(Some(5)));
assert_eq!(None, Some(3).and(None as Option<i32>));
assert_eq!(None, (None as Option<i32>).and(Some(5)));
assert_eq!(None, (None as Option<i32>).and(None as Option<i32>));

assert_eq!(Some(9), Some(3).and_then(|v| if v >= 0 { Some(v * v) } else { None }));
assert_eq!(None, Some(-3).and_then(|v| if v >= 0 { Some(v * v) } else { None }));
assert_eq!(None, (None as Option<i32>).and_then(|v| if v >= 0 { Some(v * v) } else { None }));

assert_eq!(Some(3), Some(3).or(Some(5)));
assert_eq!(Some(3), Some(3).or(None));
assert_eq!(Some(5), (None as Option<i32>).or(Some(5)));
assert_eq!(None, (None as Option<i32>).or(None));

assert_eq!(Some(3), Some(3).or_else(|| Some(5)));
assert_eq!(Some(3), Some(3).or_else(|| None));
assert_eq!(Some(5), (None as Option<i32>).or_else(|| Some(5)));
assert_eq!(None, (None as Option<i32>).or_else(|| None));
その他メソッド
  • fn take(&mut self) -> Option<T> - 要素を捨てて None になる
  • fn cloned(self: Option<&'a T>) -> Option<T> where T: Clone - clone()して参照外し
let mut a = Some(5);
assert_eq!(Some(5), a.take());
assert_eq!(None, a);
assert_eq!(None, a.take());

assert_eq!(Some(3), Some(&3).cloned());
assert_eq!(None, (None as Option<&i32>).cloned());
  • fn get_or_insert(&mut self, v: T) -> &mut T
    • Noneなら引数を中身にし、Someならそのまま。
    • そしてSomeの中身へのmut参照を返値とする。
  • fn get_or_insert_with<F>(&mut self, f: F) -> &mut T where F: FnOnce() -> T
    • Noneなら引数のクロージャを実行し、その返値を中身する。Someならそのまま。
    • そしてSomeの中身へのmut参照を返値とする。
let mut x = Some(10);
let mut y = None;
{
    let x2 = x.get_or_insert(12);
    let y2 = y.get_or_insert(12);
    assert_eq!(*x2, 10);
    assert_eq!(*y2, 12);
    *x2 = 11;
    *y2 = 13;
}
assert_eq!(x, Some(11));
assert_eq!(y, Some(13));

let mut x = Some(10);
let mut y = None;
let mut z = 1;
{
    let x2 = x.get_or_insert_with(|| { z *= 2; 12 }); 
    let y2 = y.get_or_insert_with(|| { z *= 3; 12 });
    assert_eq!(*x2, 10);
    assert_eq!(*y2, 12);
    *x2 = 11;
    *y2 = 13;
}
assert_eq!(x, Some(11));
assert_eq!(y, Some(13));
assert_eq!(z, 3);

std::result

optionモジュールと並び基本的なモジュール。
処理に成功した場合には欲しい戻り値をOkで包み、処理に失敗した場合はそのエラー情報をErrに包んで返す Result<T,E> がその主要なクラス。

列挙型 Enums

Result<T, E>のメソッド

判定メソッド
  • fn is_ok(&self) -> bool - Ok値ならtrue
  • fn is_err(&self) -> bool - Err値ならtrue
変換メソッド
  • fn as_ref(&self) -> Result<&T, &E>
    • Ok(v) -> Ok(&v)
    • Err(e) -> Err(&e)
  • fn as_mut(&mut self) -> Result<&mut T, &mut E>
    • Ok(v) -> Ok(&mut v)
    • Err(e) -> Err(&mut e)
  • fn map<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> U
    • Ok(v) -> Ok(op(v))
    • Err(e) -> Err(e)
  • fn map_err<F, O>(self, op: O) -> Result<T, F> where O: FnOnce(E) -> F
    • Ok(v) -> Ok(v)
    • Err(e) -> Err(op(e))
Option型変換メソッド
  • fn ok(self) -> Option<T>
    • Ok(v) -> Some(v)
    • Err(e) -> None
  • fn err(self) -> Option<E>
    • Ok(v) -> None
    • Err(e) -> Some(e)
取り出しメソッド
  • fn unwrap(self) -> T where E: Debug
    • Ok(v) -> v
    • Err -> panic
  • fn expect(self, msg: &str) -> T
    • Ok(v) -> v
    • Err -> panic with メッセージmsg
  • fn unwrap_or(self, optb: T) -> T
    • Ok(v) -> v
    • Err -> optb
  • fn unwrap_or_else<F>(self, op: F) -> T where F: FnOnce(E) -> T
    • Ok(v) -> v
    • Err(e) -> op(e)
  • fn unwrap_err(self) -> E where T: Debug
    • Ok -> panic
    • Err(e) -> e
  • fn expect_err(self, msg: &str) -> E
    • Ok -> panic with メッセージmsg
    • Err(e) -> e
  • fn unwrap_or_default(self) -> T where T: Default
    • Ok(v) -> v
    • Err -> T::default()
イテレータメソッド
  • fn iter(&self) -> Iter<T>
  • fn iter_mut(&mut self) -> IterMut<T>
let a: Result<i32,&str> = Ok(5);
let mut iter = a.iter();
assert_eq!(Some(&5), iter.next());
assert_eq!(None, iter.next());

let b: Result<i32,&str> = Err("err");
assert_eq!(None, b.iter().next());

let mut c: Result<i32,&str> = Ok(5);
if let Some(v) = c.iter_mut().next() {
    assert_eq!(v, &mut 5);
    *v = 10;
}
assert_eq!(Ok(10), c);
論理メソッド
  • fn and<U>(self, res: Result<U, E>) -> Result<U, E>
    • Ok -> res
    • Err(e) -> Err(e)
  • fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>
    • Ok(v) -> op(v)
    • Err(e) -> Err(e)
  • fn or<F>(self, res: Result<T, F>) -> Result<T, F>
    • Ok(v) -> Ok(v)
    • Err -> res
  • fn or_else<F, O>(self, op: O) -> Result<T, F> where O: FnOnce(E) -> Result<T, F>
    • Ok(v) -> Ok(v)
    • Err(e) -> op(e)
type Res1<'a> = Result<i32,&'a str>;
type Res2<'a,'b> = Result<&'a str,&'b str>;

// and()
assert_eq!(Ok("ok2"), (Ok(2) as Res1).and(Ok("ok2") as Res2));
assert_eq!(Err("er2"), (Ok(2) as Res1).and(Err("er2") as Res2));
assert_eq!(Err("er1"), (Err("er1") as Res1).and(Ok("ok2") as Res2));
assert_eq!(Err("er1"), (Err("er1") as Res1).and(Err("er2") as Res2));

// and_then()
assert_eq!(Ok('A'), (Ok(65) as Res1).and_then(|v: i32| if 0x21 <= v && v <= 0x7E { Ok(char::from(v as u8)) } else { Err("fail") }));
assert_eq!(Err("fail"), (Ok(0) as Res1).and_then(|v: i32| if 0x21 <= v && v <= 0x7E { Ok(char::from(v as u8)) } else { Err("fail") }));
assert_eq!(Err("err"), (Err("err") as Res1).and_then(|v: i32| if 0x21 <= v && v <= 0x7E { Ok(char::from(v as u8)) } else { Err("fail") }));
assert_eq!(Err("err"), (Err("err") as Res1).and_then(|v: i32| if 0x21 <= v && v <= 0x7E { Ok(char::from(v as u8)) } else { Err("fail") }));

// or
assert_eq!(Ok(2), (Ok(2) as Res1).or(Ok(5) as Result<i32, i32>));
assert_eq!(Ok(2), (Ok(2) as Res1).or(Err(-2)));
assert_eq!(Ok(5), (Err("err") as Res1).or(Ok(5) as Result<i32, i32>));
assert_eq!(Err(-2), (Err("err") as Res1).or(Err(-2)));

// or_else
assert_eq!(Ok(1), (Ok(1) as Res1).or_else(|e| if e.len() < 4 { Ok(e.len() as i32) } else { Err(e) }));
assert_eq!(Ok(3), (Err("abc") as Res1).or_else(|e| if e.len() < 4 { Ok(e.len() as i32) } else { Err(e) }));
assert_eq!(Err("abcde"), (Err("abcde") as Res1).or_else(|e| if e.len() < 4 { Ok(e.len() as i32) } else { Err(e) }));

std::error

トレイト Traits

  • Error
    • fn description(&self) -> &str - 必須メソッド。

std::io

読み書き関連。

モジュール Modules

  • prelude
    • 専ら use std::io::prelude::*; で使う。

構造体 Structs

カプセラー構造体2
  • BufReader - リーダー型を包み、内部バッファーを用いるリーダー型とするための構造体。
    • BufRead トレイトも自動的に実装される。
  • BufWriter - ライター型を包み、内部バッファーを用いるライター型とするための構造体。
  • LineWriter - ライター型を包み、一行おきにフラッシュを行うライター型とするための構造体。
  • Cursor - 適当な型を包んで トレイトSeekを実装するための構造体。
    • Cursor<T> where T: AsRef<[u8]>については、既にトレイトSeekの実装が済んでいる。
イテレータ型
  • Bytes - トレイト Readのメソッド bytes() で生成される、リーダーから1バイトずつ読み込み、バイト文字u8を返すイテレータ型。
  • Lines - 構造体 BufReader のメソッド lines() で返される、一行ずつに分割されたBufReadを返すイテレータ型。
  • Split - 構造体 BufReader のメソッド split() で返される、与えたバイト文字で分割されたBufReadを返すイテレータ型。
リーダー/ライター型
  • Chain - トレイト Read のメソッド chain() で生成される、[Buf]リーダーの読み込み終了後に与えた[Buf]リーダー型の読み込みが始まる[Buf]リーダー型。
  • Take - トレイト Read のメソッド take() で生成される、現在地から与えたバイト数分のデータを返す[Buf]リーダー型。
  • Repeat - 関数 repeat() で生成される、与えられたバイト文字を無限に繰り返すリーダー型。
  • Empty - 関数 empty() で生成される、常に EOF を返す、空のリーダー型。
  • Sink - 関数 sink() で生成される、dev/null と似た出力のゴミ捨て場となるライター型。正しい書き込みバイト数を返す。
  • Stdin - 標準入力構造体
  • Stdout - 標準出力構造体
  • Stderr - 標準エラー出力構造体
  • StdinLock
  • StdoutLock
  • StderrLock
その他

トレイト Traits

列挙型 Enums

  • ErrorKind - ioモジュールで用いられるエラー型。
  • SeekFrom - シークに用いられる列挙型。
    • Start(u64) - 先頭からのバイト数。
    • End(i64) - 終端からのバイト数。
    • Current(i64) - 現在地点からのバイト数。
  • CharsError - [Unstable]

関数 Functions

  • copy - 与えたリーダー型を読み込み、EOF を読むまで与えたライター型に書き込む関数。書き込みバイト数を Result に包んで返す。
  • repeat - 与えたバイト文字を繰り返す、リーダー型 Repeat のインスタンスを生成。
  • empty - 空のリーダー型 Empty のインスタンスを生成。
  • sink - 書き込みの捨て場となるライター型 Sink のインスタンスを生成。
  • stdin - 標準入力構造体 Stdin のインスタンスを生成。
  • stdout - 標準出力構造体 Stdout のインスタンスを生成。
  • stderr - 標準エラー出力構造体 Stderr のインスタンスを生成。

std::fmt

format!,println!,print! 等で使われるフォーマッティング関連。

フォーマッティング

Formatting Parameters

トレイト Traits

フォーマッティングトレイト Formatting traits

フォーマッティングの際に呼び出されるトレイト。

I/Oトレイト

関数 Functions

  • format - マクロformat_args!を併用して、format!と同等の機能を与える
  • write - マクロformat_args!を併用して、write!と同等の機能を与える

std::cmp

相当性 (equality), 順序性 (order)、部分相当 (partial equality)、部分順序 (partial order) 関係。

列挙型 Enums

  • Ordering - 順序関係を示す。
    • Ordering::Less
    • Ordering::Equal
    • Ordering::Greater

トレイト Traits

  • PartialEq
    • fn eq(&self, other: &Rhs) -> bool - 必須メソッド。二項演算子==
    • fn ne(&self, other: &Rhs) -> bool - 二項演算子!=
  • PartialOrd
    • fn partial_cmp(&self, other: &Rhs) -> Option<Ordering> - 必須メソッド
    • fn lt(&self, other: &Rhs) -> bool - 二項演算子<
    • fn le(&self, other: &Rhs) -> bool - 二項演算子<=
    • fn gt(&self, other: &Rhs) -> bool - 二項演算子>
    • fn ge(&self, other: &Rhs) -> bool - 二項演算子>=
  • Eq
    • トレイト PartialEq の二項関係 == が、「反射律」・「対称律」・「推移律」を完全に満たすとき、このトレイトの実装を宣言する。
  • Ord
    • トレイト PartialOrd の二項関係 <,<=,>,>= が、「完全律」・「反対称律」・「推移律」を完全に満たすとき、このトレイトの実装を宣言する。

関数 Functions

引数2つの最大値/最小値を返す関数。

  • max - 最大値。assert_eq!(2, cmp::max(1, 2));
  • min - 最小値。assert_eq!(1, cmp::min(1, 2));

std::iter

イテレータ関係。

構造体 Structs

以下全てイテレータ型。

トレイトIteratorのメソッドで生成されるイテレータ型。
  • Chain - トレイトIteratorのメソッドchain()で生成される、与えたイテレータを尻に結合したイテレータ型。
  • Cloned - トレイトIteratorのメソッドcloned()で生成される、複製されたイテレータ型。
  • Cycle - トレイトIteratorのメソッドcycle()で生成される、循環イテレータ型。
  • Enumerate - トレイトIteratorのメソッドenumerate()で生成される、イテレータの順番と要素をタプルで返すイテレータ型。
  • Filter - トレイトIteratorのメソッドfilter()で生成される、与えた述語 FnMut(&Self::Item) -> bool によりフィルターされた要素を返すイテレータ型。
  • FilterMap - トレイトIteratorのメソッドfilter_map()で生成される、与えた述語 FnMut(Self::Item) -> Option<B> で返された非None要素を返すイテレータ型。
  • FlatMap - トレイトIteratorのメソッドflat_map()で生成される、与えた述語FnMut(Self::Item) -> U,が返すU: IntoIteratorをフラットにして作られたイテレータ型。
  • Fuse - トレイトIteratorのメソッドfuse()で生成される、最初にNoneを返した時点で終了する(それ以降Noneを返し続ける)イテレータ型。
  • Inspect - トレイトIteratorのメソッドinspect()で生成される、要素を取り出すとき、与えた関数F: FnMut(&Self::Item) -> ()が適用されるイテレータ型。
  • Map - トレイトIteratorのメソッドmap()で生成される、与えた写像FnMut(Self::Item) -> Bにより変換された結果を返すイテレータ型。
  • Peekable - トレイトIteratorのメソッドpeekable()で生成される、要素ののぞき見peek()が可能なイテレータ型。
  • Rev - トレイトIteratorのメソッドrev()で生成される、逆順イテレータ。逆順に要素を取得できるDoubleEndedIteratorにのみ可能。
  • Scan - トレイトIteratorのメソッドscan()で生成される、与えた初期値と述語F: FnMut(&mut St, Self::Item) -> Option<B>が返す要素を順次返すイテレータ型。メソッドfold()に似ている。
  • Skip - トレイトIteratorのメソッドskip()で生成される、最初のn個の要素をスキップするイテレータ型。
  • SkipWhile - トレイトIteratorのメソッドskip_while()で生成される、与えた述語がP: FnMut(&Self::Item) -> booltrueを返す要素をスキップするイテレータ型。falseが返された時点でスキップは終了。
  • Take - トレイトIteratorのメソッドtake()で生成される、最初のn個の要素を取り出すイテレータ型。
  • TakeWhile - トレイトIteratorのメソッドtake_while()で生成される、与えた述語がP: FnMut(&Self::Item) -> booltrueを返す要素を取り出すイテレータ型。falseが返された時点で取り出しは終了。
  • Zip - トレイトIteratorのメソッドzip()で生成される、与えたイテレータとの2つの要素を順次タプルを返すイテレータ型。返される要素の数は、2つのイテレータの最小数。
関数で生成されるイテレータ型。
  • Empty - 関数std::iter::emptyが返す空イテレータ型。
  • Once - 関数std::iter::once()で生成される、1要素イテレータ型。
  • Repeat - 関数std::iter::repeatで生成される、1要素を無限に繰り返す循環イテレータ型。

トレイト Traits

  • Iterator - イテレータの満たすべきトレイト
    • fn next(&mut self) -> Option<Self::Item> - 必須メソッド
  • DoubleEndedIterator - 両端イテレータ
  • FromIterator - イテレータからの変換。
    • Tがこのトレイトを実装していると、イテレータのcollect()メソッドにより、イテレータ→Tへの変換が可能。
      • 例えば、let set: HashSet<_> = ["A", "B", "C"].into_iter().collect();
      • collect()メソッドは何かと有用なので覚えておこう。
  • IntoIterator - イテレータへの変換。
    • selfを消費 (consume) してイテレータへと変換されることに注意
      • selfを借用して要素の参照のイテレータを返すiter()メソッドとの違いに注意
  • ExactSizeIterator - 正確な大きさを与えるイテレータ。
  • Extend - イテレータに引数のイテレータを付け加える。
    • fn extend<T>(&mut self, iter: T) - 必須メソッド。

関数 Functions

  • empty - 空のイテレータ型 Empty のインスタンスを生成。
  • once - 1要素のイテレータ型 Once のインスタンスを生成。
  • repeat - 1要素を無限に繰り返す循環イテレータ型、Repeat のインスタンスを生成。

std::collections

モジュール Modules

構造体 Structs

列挙型 Enum

std::hash

ハッシュ関係。

トレイト Traits

  • Hash - HashMap,HashSet で用いられるハッシュを与える。
    • fn hash<H>(&self, state: &mut H) where H: Hasher - 必須メソッド。state にハッシュを与え更新する。
  • Hasher - ハッシュを計算・保持するためのトレイト。
    • fn finish(&self) -> u64 - 計算したハッシュ結果を返す。
    • fn write(&mut self, bytes: &[u8]) - バイト列を受け取り内部ハッシュを更新する。
  • BuildHasher

std::fs

ファイル操作関連。

構造体 Structs

  • File - ファイル読み書き。C言語のFILE的な。
    • std::ioRead,Write,Seek トレイトを実装しており、読み書きシークが可能。
  • ReadDir - ディレクトリ読み込み。
  • DirBuilder
  • DirEntry
  • FileType - 構造体 DirEntry,Metadata のメソッドfile_type()により生成されるファイルタイプ型。
  • Metadata - 関数std::fs::metadata()において、パスを与えると得られるメタデータ型。
  • OpenOptions
  • Permissions

関数 Functions

  • canonicalize - 与えたパスを正規化してstd::io::Resultで包んで返す。
  • copy - 与えたfromパスにあるファイルをtoパスにコピーする(上書きする)。コピーしたバイト数をstd::io::Resultで包んで返す。
  • create_dir - 与えたパスにディレクトリを作成する。
  • create_dir_all - 与えたパスにディレクトリを再帰的に作成する。
  • hard_link - 与えたsrcパスのファイルをdstパスにハードリンクを作る。
  • metadata - 与えたパスからメタデータ型Metadataを返す。
  • read_dir - 与えたパスのディレクトリからReadDirstd::io::Resultで包んで返す。
  • read_link - 与えたパスのシンボリック・リンクから示しているパスPathBufを取得し、std::io::Resultで包んで返す。
  • remove_dir - 与えたパスの(空の)ディレクトリを削除して、std::io::Result<()>を返す。
  • remove_dir_all - 与えたパスのディレクトリを中身ごと削除して、std::io::Result<()>を返す。
  • remove_file - 与えたパスのファイルを削除して、std::io::Result<()>を返す。
  • rename - 与えたfromパスにあるファイルをtoパスにリネームする、std::io::Result<()>を返す。
  • set_permissions - 与えたパスのファイル/ディレクトリのパーミッションを設定し、std::io::Result<()>を返す。
  • soft_link - [Deprecated]
  • symlink_metadata - 与えたパスを、シンボリックリンクを辿ることなくメタデータ型Metadataを取得しstd::io::Resultで包んで返す。

std::path

構造体 Structs

  • Path - パスのスライス(文字列における &str と同類)
  • PathBuf - 可変なパス(文字列における String と同類)
  • Iter - パスの各部分パス (OsStr) を走るイテレータ。
  • Components - パスの各コンポーネント (enum Component) を走るイテレータ。
  • Display
  • PrefixComponent
  • StripPrefixError

列挙型 Enums

  • Component - パスのコンポーネントを表す。
    • Prefix(PrefixComponent<'a>)
    • RootDir
    • CurDir
    • ParentDir
    • Normal(&'a OsStr)
  • Prefix - Windows only のパスのプレフィックス(C:など)

定数 Constants

  • MAIN_SEPARATOR - (現在のプラットフォームでの)パス・セパレータ。

関数 Functions

  • is_separator - 与えた文字型cが現在のプラットフォームでファイルパスのセパレータであるか判別する。

std::ffi

FFI (Foreign function interface) 関連。

構造体 Structs

std::slice

構造体 Structs

以下全てイテレータ型

  • Chunks - プリミティブ型 slice のメソッドchunks()において生成される、与えた個数分イテレータの要素をタプルにして返すイテレータ型。
  • ChunksMut - プリミティブ型 slice のメソッドchunks_mut()において生成される、与えた個数分イテレータの要素をmutableなタプルにして返すイテレータ型
  • Iter - 様々な構造体のメソッドiter() において生成される、イテレータ型。
  • IterMut - 様々な構造体のメソッドiter_mut()において生成される、mutableなイテレータ型。
  • Split - 様々な構造体のsplit()メソッドにおいて生成される、与えた述語F: FnMut(&T) -> booltrueの要素を排除し、残された部分をスライスして返すイテレータ。
  • SplitMut - 様々な構造体のメソッドsplit_mut()において生成される、与えた述語F: FnMut(&T) -> booltrueの要素を排除し、残された部分を mutable なスライスにして返すイテレータ型。
  • SplitN- 様々な構造体のメソッドsplitn()において生成される、与えた述語F: FnMut(&T) -> booltrueの要素を取り除き、取り除いた箇所で分割し与えた数n個のスライスにして返すイテレータ型。
  • SplitNMut - 様々な構造体のメソッドsplitn_mut()において生成される、与えた述語F: FnMut(&T) -> booltrueの要素を取り除き、取り除いた箇所で分割し与えた数n個の mutable なスライスにして返すイテレータ型。
  • RSplitN - 様々な構造体のメソッドrsplitn()において生成される、SplitNの逆順イテレータ型。
  • RSplitNMut - 様々な構造体のメソッドrsplitn_mut()において生成される、mutableな逆順イテレータ型。
  • Windows - プリミティブ型sliceのメソッドwindows()において生成される、イテレータの要素配列を与えたn個のタプルにして順次返すイテレータ型。nがイテレータ要素より大きいとき、このWindowsイテレータは値を返さない。

トレイト Traits

関数 Functions

  • from_raw_parts - 与えられたポインタとサイズから、スライス参照を生成する。
    • unsafe fn from_raw_parts<T>(p: *const T, len: usize) -> &'a [T]
  • from_raw_parts_mut

std::mem

メモリ関連。

定数 Constants

[Unstables]

関数 Functions

  • replace - mutable な場所にある値を、与えられた新しい値に代入し、古い値を返す。
  • swap - mutable な場所にある2つの値を交換する。
  • unreachable - [Experimental] 未定義動作を起こすことで、この関数が用いられる箇所にたどり着くコードが存在しないとコンパイラに指示し、その様に最適化が行われる。
型サイズ/変数配置/型情報についての関数
  • size_of, size_of_val - 与えた型/引数のサイズ(バイト)を返す
  • align_of, align_of_val - 与えた型/引数の最小アライメントを返す。
  • discriminant - 列挙型Tの参照を引数に取り、列挙型の各ヴァリアントについてユニークな値 Discriminant<T> を返す。
use std::mem::{size_of, size_of_val, align_of, align_of_val, needs_drop};

assert_eq!(size_of::<()>(), 0);
assert_eq!(size_of::<char>(), 4);
assert_eq!(size_of::<i64>(), 8);

assert_eq!(size_of_val(b"1234567890"), 10);
assert_eq!(size_of_val(&0u8), 1);
assert_eq!(size_of_val(&0.0f32), 4);

assert_eq!(align_of::<()>(), 1);
assert_eq!(align_of::<[u8; 4]>(), 1);
assert_eq!(align_of::<i16>(), 2);

assert_eq!(align_of_val(&0u8), 1);
assert_eq!(align_of_val(&0u16), 2);
drop 関係関数
  • forget - デストラクタの呼び出し等を回避する。
    • 危険な std::mem::uninitialized() で作った構造体を最終処理するときなどに使用。
  • drop - 値を処分(強制Drop)する。Copy を実装している型には作用しない(関数引数に move して値を処分するため)。
  • needs_drop - Drop が必要か否かを返す。最適化に用いる。
use needs_drop;

assert!(needs_drop::<i32>(), false);
assert!(needs_drop::<&str>(), false);
assert!(needs_drop::<String>(), true);
assert!(needs_drop::<Box<i32>>(), true);
unsafeな関数
  • transmute - 型を変換する。サイズはチェックするが、不完全。
  • transmute_copy - 型を変換してコピー。
  • uninitialized - デフォルトの初期化チェッカーを迂回する。
  • zeroed - 指示した型についてゼロフィルされた値を返す。

構造体 Structs

  • Discriminant - 列挙型の各ヴァリアントを区別する不透過型。EqHash等のトレイトを実装する。関数 discriminant の返値。
#[derive(Debug)]
enum Foo {
    A(&'static str),
    B(i8),
    C(i8),
}

fn main() {
    use Foo::*;
    use std::mem::discriminant;
    println!("{:?}", discriminant(&A("Hello"))); // Discriminant(0)
    println!("{:?}", discriminant(&A("World"))); // Discriminant(0)
    println!("{:?}", discriminant(&B(0))); // Discriminant(1)
    println!("{:?}", discriminant(&B(1))); // Discriminant(1)
    println!("{:?}", discriminant(&C(0))); // Discriminant(2)
    println!("{:?}", discriminant(&C(1))); // Discriminant(2)
    
    struct Bar(u8, u8);
    println!("{:?}", discriminant(&Bar(0, 1))); // Discriminant(0)
    println!("{:?}", discriminant(&Bar(2, 3))); // Discriminant(0)
}

共用体 Unions

std::ptr

生ポインタ操作。

構造体 Structs

[Unstable]

関数 Functions

  • copy - C言語のmemcpy的な。
  • copy_nonoverlapping
  • drop_in_place - デストラクタを強制実行する。
  • null - ヌルポインタを作る。
  • null_mut - mutable なヌルポインタを作る。
  • read - ポインタから値を読み込む。
  • read_volatile
  • replace - mutable な場所にある値を、与えられた新しい値に代入し、古い値を返す。
    • 生ポインタを扱う点以外は、std::mem::replace とほぼ同じ。
  • swap - mutable な場所にある2つの値を交換する。
  • write - ポインタに値を書き込む。
  • write_bytes
  • write_volatile
  • read_and_drop - [Unstable]

std::boxed

ヒープ上にアロケイトされた型。

構造体 Structs

定数 Constants

[Unstable]

トレイト Traits

std::cell

構造体 Structs

スマートポインタ型
エラー型

列挙型 Enums

std::rc

構造体 Structs

  • Rc - 参照カウント型。
  • Weak - Rcの弱い参照。

std::borrow

列挙型 Enums

  • Cow<'a, B> - COW (clone-on-write) を行うスマートポインタ型
    • Borrowed(&'a B)
    • Owned(<B as ToOwned>::Owned)

トレイト Traits

  • Borrow - 借用の抽象化。See BorrowとAsRef
  • BorrowMut - 同上
  • ToOwned - Cloneと似ているが、Borrowな関係にある型間の変換をも行う(str->String等の)

std::clone

トレイト Traits

  • Clone - 明示的にオブジェクトを複製するためのトレイト。

std::marker

構造体 Structs

  • PhantomData - C++のテンプレート黒魔術みたく、コンパイル時に色々とするために使う?

トレイト Traits

std::default

トレイト Traits

  • Default - Default::default() でデフォルト値を一律に扱う。

std::env

モジュール Modules

  • consts - 現在のターゲットについての定数群を持つモジュール。

構造体 Structs

  • Args - プロセスの引数を Option<String> で返すイテレータ。
  • ArgsOs - 上の StringOsStringで。
  • Vars - 環境変数を Option<(String, String)> で返すイテレータ。
  • VarsOs - 上の StringOsStringで。
  • JoinPathsError - 関数join_pathsで失敗したときに返る
  • SplitPaths - パスが:で結合したPATH環境変数をパースして、分割されたパスをOption<PathBuf>で返すイテレータ。

列挙型 Enums

  • VarError - std::env::*の関数群で使われるエラー型。

関数 Functions

  • args - Argsを返す。
  • args_os - ArgsOsを返す。
  • vars - Varsを返す。
  • vars_os - VarsOsを返す。
  • var - 与えたキーから環境変数の内容を Result<String, VarError> で返す。
  • var_os - 上の StringOsStringで。
  • current_dir - カレント・ワーキング・ディレクトリのパスをResult<PathBuf>で返す。
  • set_current_dir - カレント・ワーキング・ディレクトリのパスをセットする。
  • current_exe - 実行ファイルのパスをResult<PathBuf>で返す。
  • home_dir - ホームディレクトリのパスを Option<PathBuf> で返す。
  • join_paths - PATH環境変数にパスを追加する。
  • set_var - 環境変数を追加する。
  • remove_var - 環境変数を削除する。
  • split_paths - AsRef<OsStr>なパスをSplitPathsに分割する。
  • temp_dir - テンポラリ・ディレクトリのパスをPathBufで返す。

std::any

構造体 Structs

  • TypeId - 型のユニークIDを表す。

トレイト Traits

  • Any - 動的型付けをエミュレイトするためのトレイト。
    • fn get_type_id(&self) -> TypeId - [Unstable]必須メソッド。

std::num

数値型の追加関連。

構造体 Structs

  • ParseFloatError - f32f64によるトレイトstd::str::FromStrの実装におけるエラー型。
  • ParseIntError - 関数i8::from_str_radixにおけるエラー型。
  • Wrapping - オーバーフローしないよう数値型をラップする型。(ただしゼロ除算ではpanicする)
  • TryFromIntError - [Unstable]

列挙型 Enums

  • FpCategory - f64,f32のメソッドclassify()が返す、浮動小数点数の分類のための列挙型。
    • Nan
    • Infinite
    • Zero
    • Subnormal
    • Normal

トレイト Traits

[Deprecated],[Unstable]

std::time

構造体 Structs

  • Duration - 時間 (span of time) を表す型。粒度はナノセカンド。
  • Instant - 時間経過を測る型。Durationを返す。
  • SystemTime - システムクロックを測る。
  • SystemTimeError - SystemTime::duration_since()で使われるエラー型。

定数 Constants

  • UNIX_EPOCH - エポックタイムをSystemTime型で返す。

std::sync

モジュール Modules

構造体 Structs

列挙型 Enums

定数 Constants

  • ONCE_INIT - Onceの初期値。

型定義 Type Definitions

  • LockResult
    • type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;
  • TryLockResult
    • type TryLockResult<Guard> = Result<Guard, TryLockError<Guard>>;

std::thread

構造体 Structs

関数 Functions

  • current - 現在のスレッドThreadを返す。
  • panicking - スレッドがpanicより巻き戻って (unwinding) しているかを返す。
  • park - スレッドを一時停止 (park) させる。
  • park_timeout -
  • park_timeout_ms - [Deprecated]
  • sleep - 与えた時間 (Duration型) スリープする。
  • sleep_ms - [Deprecated]
  • spawn - 新しいスレッドを作り、JoinHandle を返す。
  • yield_now - OSのスレッド・スケジューラーに対し、(現在のスレッドの)タイムスライスを終了させる。
use std::{thread, time};

let a_sec = time::Duration::from_secs(1);

println!("Wait a second.");
thread::sleep(a_second);
println!("Now.");

型定義 Type Definitions

  • Result - JoinHandle::join()で使われる。
    • type Result<T> = Result<T, Box<Any + Send + 'static>>;

std::net

TCP/UDP 通信関連。

std::sync::atomic

構造体 Structs

列挙型 Enums

  • Ordering
    • Relaxed
    • Release
    • Acquire
    • AcqRel
    • SeqCst

定数 Constants

関数 Functions

  • fence - 順序atomic::Orderingを与えフェンスとして働く。

std::sync::mpsc

複数プロデューサー・単独コンシューマーの、FIFO (First-In-First-Out) キュー伝達関係。

構造体 Structs

列挙型 Enums

関数 Functions

  • channel - 新しい非同期チャンネル (Sender<T>, Receiver<T>) を返す。
  • sync_channel - 新しい同期チャンネル (SyncSender<T>, Receiver<T>) を返す。

サンプル

型名表示

デバッグのため型名を表示したいときは、関数std::intrinsics::type_nameを使う。
関数type_name()は与えられた型パラメータTの型名を&'static strで返す。

#![feature(core_intrinsics)]

fn get_type_of<T>(_: &T) -> &'static str {
    unsafe { std::intrinsics::type_name::<T>() }
}

let a = &10.0f32;
println!("{:?}'s type name is {}", a, get_type_of(&a));

型変換

失敗するかも知れない変換は、std::convert::TryFromトレイトを使う。

#![feature(try_from)]
use std::convert::TryFrom;

println!("{:?}", u8::try_from(32i32));

数値型から C-like 列挙型へ

#[derive(Debug, Copy, Clone)]
enum Color {
    Red   = 0xFF0000,
    Green = 0x00FF00,
    Blue  = 0x0000FF,
}

use std::convert::TryFrom;
impl TryFrom<u32> for Color {
    type Error = String;
    
    fn try_from(value: u32) -> Result<Self, Self::Error> {
        use Color::*;
        
        let color = match unsafe { std::mem::transmute(value) } {
            Red => Red,
            Green => Green,
            Blue/* means _ */ => Blue,
        };
        if value == color as u32 {
            Ok(color)
        } else {
            Err(format!("can't convert 0x{:08X} to Color", value))
        }
    }
}

カスタム範囲..構文実装

Stepトレイトを実装させることで、任意の構造体について範囲構文がイテレータとなる。

#![feature(step_trait)]

#[derive(PartialEq,PartialOrd,Clone,Debug)]
struct Foo {
    val: i32,
}

impl Foo {
    fn new(v: i32) -> Self {
        Self { val: v }
    }
}

use std::iter::Step;

impl Step for Foo {
    fn steps_between(start: &Self, end: &Self) -> Option<usize> {
        if start <= end {
            Some((end.val - start.val) as usize)
        } else {
            None
        }
    }
    fn replace_one(&mut self) -> Self {
        self.val = 1;
        self.clone()
    }
    fn replace_zero(&mut self) -> Self {
        self.val = 0;
        self.clone()
    }
    fn sub_one(&self) -> Self {
        Self::new(self.val - 1)
    }
    fn add_one(&self) -> Self {
        Foo::new(self.val + 1)
    }
    fn add_usize(&self, n: usize) -> Option<Self> {
        Some(Foo::new(self.val + n as i32))
    }
}

fn main() {
    let x: std::ops::Range<Foo> = Foo::new(1) .. Foo::new(10);
    
    for a in Foo::new(0) .. Foo::new(4) {
        println!("{:?}", a);
    }
}

関数呼び出し演算子オーバーロード

FnOnce -> FnMut -> Fn の順での実装が必要とされる。

以下では Args=(&String,)という、&String のみを引数とする関数を実装している。

  • FnOnceのみの実装の場合、selfは消費 (consume) されるので、続けて関数呼び出しを行うことは出来ない。
    • 呼び出されるのは、当然 call_once
  • FnOnce,FnMutのみの実装の場合、複数回関数呼び出しは出来るが、let mut foo = ... とする必要がある。
    • 呼び出されるのは、全て call_mut
    • let foo = ...call_once を呼ぶことは出来ない。
      • Foo::call_once(foo, (&text,));call_once を呼ぶことは出来る。
  • FnOnce,FnMut,Fn全て実装の場合、複数回関数呼び出しが出来る
    • 呼び出されるのは、全て call
      • let mut foo = ... としても、call_mut は呼び出されない。
        • Foo::call_mut(&mut foo, (&text,));call_mut を呼ぶことは出来る。
        • Foo::call_once(foo, (&text,));call_once を呼ぶことは出来る。
      • foo(&text) の代わりに、Foo::call(&foo, (&text,)); で呼ぶことが出来る。
#![feature(fn_traits, unboxed_closures)]
use std::ops::{FnOnce, FnMut, Fn};

struct Foo { }

impl<'a> FnOnce<(&'a String,)> for Foo {
    type Output = ();
    
    extern "rust-call" fn call_once(self, (arg,): (&String,)) -> Self::Output {
        println!("Hello {}! at once", arg);
    }
}

impl<'a> FnMut<(&'a String,)> for Foo {
    extern "rust-call" fn call_mut(&mut self, (arg,): (&String,)) -> Self::Output {
        println!("Hello {}! as mut", arg);
    }
}

impl<'a> Fn<(&'a String,)> for Foo {
    extern "rust-call" fn call(&self, (arg,): (&String,)) -> Self::Output {
        println!("Hello {}!", arg);
    }
}

fn main() {
    let foo = Foo { };
    let text = "World".to_string();
    
    foo(&text); // Hello World!
    Foo::call(&foo, (&text,)); // Hello World!
    Foo::call_mut(&mut foo, (&text,)); // Hello World! as mut
    Foo::call_once(foo, (&text,)); // Hello World! at once
}

1要素タプル構造体を用いた、非自前構造体のメソッド実装

標準ライブラリなどの外部に書かれたカスタム型には、自分ではメソッドを書くことが出来ない。
そこで、その型の参照を包んだ1要素タプル構造体を作ることで、最小のコストでメソッドを追加することが出来る。参考: How to print! an Option>?

例として、Option<Box<T>>型のラッパーを書く。これはわざわざラッパーを書くまでもない例だが、参考までに。

struct Node<T> {
    val: T,
    node: Option<Box<Node<T>>>,
}

struct Wrapper<'a, T: 'a>(&'a Option<Box<T>>);

impl<T> Node<T> {
    
    fn new(val: T, node: Option<Box<Node<T>>>) -> Self {
        Node {
            val,
            node,
        }
    }
    
    fn node_wrapper(&self) -> Wrapper<Self> {
        Wrapper(&self.node)
    }
}

use std::fmt::*;

impl<T> Display for Node<T> where T: Display {
    
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{{val: {}, node: {}}}", self.val, self.node_wrapper()).unwrap();
        Ok(())
    }
    
}

impl<'a, T> Display for Wrapper<'a, T> where T: Display {
    
    fn fmt(&self, f: &mut Formatter) -> Result {
        match *(self.0) {
            Some(ref body) => write!(f, "{}", body),
            None => write!(f, "none!"),
        }.unwrap();
        Ok(())
    }
    
}

fn main() {
    let node = Node::new(1, None);
    let node = Node::new(2, Some(Box::new(node)));
    println!("{}", node);
}

構造体のスライス変換

use std::ops;
use std::mem;

#[derive(Debug)]
struct AType {
    a: i32,
    b: i32,
}

impl ops::Deref for AType {
    type Target = [i32];

    fn deref(&self) -> &Self::Target {
        let array: &[i32; 2] = unsafe { mem::transmute(self) };
        &array[..]
    }
}

fn main() {
    let a = AType { a: 10, b: 20 };
    println!("a: {:?}", a);
    let b: &[i32] = &a;
    println!("b: {:?}", b);
}

unreachable()を用いた最適化

std::mem::unreachable を用いて最適化が行えるのは上で説明したが、その実際のサンプル。

unreachable_test.rs
#![feature(unreachable)]
#![crate_type = "lib"]

use std::mem;

pub fn unreachable_test_fn(v: i32) -> i32 {
    let rem = v % 3;
    if rem == 0 {
        577
    } else if v == 1 {
        2999
    } else {
        unsafe {
            mem::unreachable();
        }
    }
}

このテスト関数に最適化を行うと、if rem == 0 { 577 } else { 2999 } と変換されることが期待される。

unreachable_main.rs
extern crate unreachable_test;

use self::unreachable_test::unreachable_test_fn;

fn main() {
    println!("f(0) = {}", unreachable_test_fn(0));
    println!("f(1) = {}", unreachable_test_fn(1));
    println!("f(2) = {}", unreachable_test_fn(2));
    println!("f(3) = {}", unreachable_test_fn(3));
    println!("f(4) = {}", unreachable_test_fn(4));
    println!("f(5) = {}", unreachable_test_fn(5));
}
  • -Oオプションは最適化 (-C opt-level=2) を指示する。
    • 最適化を指示しない場合、mem::unreachable() に達すると stack overflow 等のエラーが生じる。
  • -Lオプションはライブラリの有る場所を指示する。
$ rustc -O unreachable_test.rs
$ rustc -L . unreachable_main.rs
$ ./unreachable_main 
f(0) = 577
f(1) = 2999
f(2) = 2999
f(3) = 577
f(4) = 2999
f(5) = 2999

期待通りの挙動が示された。

  1. ユニット型以外を返そうとする議論もあったようだが、潰えてる。

  2. new(inner: T) とかやって他の構造体をカプセルして生成される構造体を、適当にこう呼ぶことにした。

105
106
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?