プロジェクト

全般

プロフィール

Rust言語をかじる

言語仕様

型のインポート

  • prelude と呼ばれる予めインポートされる型のリストに含まれる型は、use文で指定せずに使用可能

整数型

サイズ 符号付 符号なし
8bit i8 u8
16bit i16 u16
32bit i32 u32
64bit i64 u64
8bit i8 u8
arch1 isize usize

1 計算機アーキテクチャにより決まる。32bitアーキテクチャなら32bit、64bitアーキテクチャなら64bit

浮動小数点型

IEEE-754に基づく 単精度浮動小数点 f32 と、倍精度浮動小数点 f64

タプル型

let tup: (i32, f64, u8) = (500, 6.4, 1);

タプルの各要素の取り出しは、分配もしくはインデックス指定

分配  let (x, y, z) = tup;
インデックス指定 let first = tup.0

配列型

let a = [ 1, 2, 3, 4, 5 ];

取り出し let first = a[0];

要素はすべて同じ型となります。主にスタックで扱う型で、固定長。
配列よりはベクタ型(std::vecモジュール)を使うのが一般的。

構造体

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
  • structキーワードで構造体宣言
  • 型名はキャメルケース
  • 波括弧内にフィールド(構造体を構成するデータ片の名前と型)を羅列
構造体のインスタンス生成
let user1 = User {
    email: String.from("someone@example.com"),
    username: String.from("someusername123"),
    active: true,
    sign_in_count: 1,
};
  • 構造体の型名の後に波括弧で key: valueペアを羅列、keyはフィールドの名前、valueはフィールドの値
  • 順不同で指定可能
  • フィールド初期化省略記法あり(フィールド名と値を指定する変数名が同一のとき、keyの指定を省略しvalueだけ記述)
  • 構造体更新記法あり(同じ構造体の他のインスタンスからインスタンスを生成し、フィールドを同じ値とする)
構造体のフィールドアクセス

user1.email = String.from("anotheremail@example.com");

タプル構造体

struct Color(i32, i32, i32);

  • フィールド名がない
  • フィールドへのアクセスはタプル型と同じ(分配、または添え字でアクセス)
ユニット様構造体
  • フィールドが空(ユニット型と似た振る舞い)
    ある型にトレイトを実装するが型自体に保持するデータがないときに有用

enum型

関数型言語の代数的データ型に酷似

enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;

enumの列挙子に値を持たせる
enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String.from("127.0.0.1"));

  • 列挙子毎に持たせる値の型、数は異なってもよい
enumにメソッドを定義

ジェネリック型

構造体のジェネリック型
struct Point<T> {
    x: T,
    y: T,
}
enumのジェネリック型
enum Option<T> {
    Some(T),
    None,
}

トレイト

共通の振る舞いを抽象的に定義する。(他の言語におけるインタフェースのようなもの)

pub trait Summary {
    fn summarize(&self) -> String;
}
  • traitキーワードを使用し、トレイト名を指定してトレイトを定義
  • メソッドシグニチャを定義(メソッド本体はこのトレイトを実装する型が提供する)
トレイトの実装
pub struct NewArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, selft.location)
    }
}
  • implキーワードの後に、実装したいトレイト名を書き、forキーワードを置いてトレイト実装対象の型名を指定
  • implブロック内にトレイトで定義したメソッドシグニチャを置き、メソッド本体を記述
デフォルト実装
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

トレイトを実装する型では、デフォルト実装を保持するかオーバーライドするかを選択可

注記
  • トレイトまたは対象の型が自分のクレートに固有であること

変数

値を変数に保持

  • let文で変数を生成
  • デフォルトでは不変、可変にする場合は let mut とする
    let mut guess = String::new();

定数

const MAX_POINTS: u32 = 100_000;

  • constキーワードで宣言、必ず型名を注釈
  • 定数名は、すべて大文字で単語区切りはアンダースコア

シャドーイング

同じスコープで既に宣言した変数名と同じ変数名を新しく宣言すると、以降最初の変数は覆い隠される。

関数の種類

関数

fn plus_one(x: i32) -> i32 {
    x + 1
}
  • fnキーワードで関数宣言
  • 関数名は小文字スネークケース
  • 引数は丸括弧内に仮引数名、コロン、型名の順で指定
  • 戻り値は -> の後に型名指定
  • 関数本体は波括弧内に文の並び、最後は式でもよい(戻り値となる)
    • 関数本体の最後を式にするときは、式の末尾にセミコロンを置かない(置くと文になり明示的なreturn文が必要)
    • 開き波括弧は、関数宣言の行の末尾に置く

関連関数

形名::関数名 で呼び出す、型の関連関数。他の言語では静的(スタティック)メソッドに相当。

struct Rectangle {
    width: u32,
    height: u32,
}
impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}
  • implブロックは複数記述可能

メソッド

struct Rectangle {
    width: u32,
    height: u32,
}
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
  • 構造体Rectangleに対してメソッドareaを定義
  • メソッドは第1引数を&self (構造体のフィールドを変更しない場合)

関数のエラー通知

Rust に例外機構はない。

  • 関数からの回復可能なエラー通知には一般に Result 列挙型を戻り値型として使用する。
  • 関数での回復不能なエラーには実行を中止する(panic!マクロ)。

Result型の列挙子は Ok, Err

enum Result<T, E> {
    Ok(T),
    Err(E),
}
使い方例1(matchで判別)
let f = File::open("hello.txt");
let mut f = match f {
    Ok(file) => file,
    Err(e) => return Err(e),
};
使い方例2(回復不能の失敗)
let f = File::open("hello.txt").expect("Failed to open hello.txt");
  • 失敗したときはexpectの引数に渡したメッセージとともにpanicでプログラム実行を中止

io::Result の expectメソッドは戻り値が Err のときに引数として渡された文字列を表示しプログラムをクラッシュさせる

使い方例3(エラーの委譲)

?演算子を使用

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;  // ①
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
  • ① open関数の戻り値であるResultがOkのときはOkの中身がこの式から返る。ResultがErrのときは呼び出し元のコードにエラー値を委譲(read_username_file関数からreturnしErrを返す)

次は上述コードをさらに簡略化(メソッドチェーン的に記述)

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

ジェネリック型の関数

関数を汎用にするため、引数の型や戻り値型をジェネリック型にする。
fn largest<T>(list: &[T] -> T {

  • ジェネリックの型名宣言を関数名と引数リストの間に山括弧
構造体のメソッドをジェネリック型
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

トレイトを引数に取る関数(≠メソッド)

独立した関数の引数にトレイト(を実装する任意の型)を取る関数を定義するには、
pub fn notify(item: &impl Summary) { ... }

引数の型は、implキーワードとトレイト名を指定する。この記述は次のトレイト境界のシンタックスシュガーとなっている。
pub fn notify<T: Summary>(item: &T) { ... }

複数のトレイト境界の指定

引数に複数のトレイトを実装する型を要求する場合、+構文を使用
pub fn notify(item: &impl Summary + Display) { ... }

トレイト境界での記述は
pub fn notify<T: Summary + Display>(item: &T) { ... }

where句でトレイト境界を記述

例えば、次のトレイト境界記述は
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 { ... }

次のように

fn some_function<T, U>(t: &T, u: &U) -> i32 
    where T: Display + Clone,
          U: Clone + Debug
{

トレイトを戻り値に返す関数

fn returns_summarizable() -> impl Summary { ... }

ライフタイム引数

例えば、関数の引数で2つの参照を取り、そのどちらかを戻り値として返す関数は、ライフタイム注釈が必要となる。
ライフタイム引数の注釈は、アポストロフィーで始め、通常全部小文字で短い名前を指定する(多くの場合、'aが使われる)。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }

フロー制御

if式

loop

while

for

コレクションの各要素に対してコードの実施
let a = [10, 20, 30, 40, 50];   // コレクション(配列)の定義
for element in a.iter() {       // 配列の各要素に対して処理を実施
    println!("the value is: {}", element);
}
繰り返し回数を指定して
for number in (1..4).rev() {
    println!("{}!", number);
}

これは、1, 2, 3 に対して逆順でコードを実施しています。

範囲

..

0..3 は、0, 1, 2 を示す。
0..=3 は、0, 1, 2, 3 を示す。

モジュール

コードのグループ化、およびアクセス制御(外部へ公開か非公開か)

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() { ... }
        fn seat_at_table() { ... }
    }
    mod serving {
        fn take_order() { ... }
        fn serve_order() { ... }
    }
}
  • mod キーワードでモジュールを定義、モジュールは入れ子にできる
  • モジュールはツリー構成
  • モジュールの中にある要素を指し示すパスはクレートルートからの絶対パス、現在のモジュールからの相対パスがある
    crate::front_of_house::hosting::add_to_waitlist(); 絶対パスでの指定例
    front_of_house::hosting::add_to_waitlist(); 相対パスでの指定例

pub でモジュール外に公開

  • 公開APIは、pubキーワードを付ける
    pub fn eat_at_restaurant() { ... }
    • 構造体のフィールドはpubを付けないとモジュール外部からのアクセスができない

use でモジュールのパスをスコープに持ち込む

  • useキーワードでパスをスコープに持ち込む(useで指定したパスはコード中で記述しなくてもよい)
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
  • 構造体、enum その他の要素をuseで持ち込むときは、フルパスを書くのが慣習
  • 同じ名前の別モジュールの要素を持ち込むと名前が衝突する
    • 親モジュール名をuseで持ち込み、親モジュールをパス指定して使用する
    • 片方の要素をasで別名(エイリアス)で使用する
      use std::io::Result as IoResult;

クレート

種類 内容 クレートルート
lib クレート ライブラリを作成する src/lib.rs
bin クレート 実行可能ファイルを作成する src/main.rs
  • プロジェクトにはクレートルートと呼ぶルートモジュールを作るソースファイルが必要
  • クレート名はパッケージと同じ名前
  • パッケージは、1個以上のクレート(ライブラリまはたバイナリ)を持つ
  • パッケージは、0個または1個のライブラリクレートを持つ(2個以上は不可)
  • パッケージは、複数のバイナリクレートを持つことができる

モジュールは、クレート内のコードをグループ化し、可視性制御(公開または非公開)する。

モジュールのファイル分割

ライブラリクレートのクレートルートファイル(lib.rs)からモジュールを参照する。

src
  +-- lib.rs         ← クレートルート
  +-- alfa.rs        ← モジュールalfaのソースファイル
  • lib.rs
    mod alfa;
    // ...
    
  • alfa.rs
    このファイルに記述した定義は、lib.rsからは mod alfa {...} されているものとして扱われる

クロージャ

変数に保存したり、引数として他の関数に渡すことができる匿名関数がクロージャ。
クロージャは、呼び出されたスコープの値をキャプチャ可能。

let expensive_closure = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

変数 expensive_closure にクロージャ自身を保存(クロージャの結果の値ではない)

  • 1組の縦棒でクロージャの定義が開始、縦棒の中にはクロージャの仮引数を指定(この例では num という名前の引数が1つ)
  • 引数の後ろに波括弧でクロージャの本体を記述(クロージャ本体が式1つしかない場合は波括弧省略可)
  • クロージャの末尾(閉じ波括弧の直後)にセミコロンが必要(let文を完成させるため)
  • クロージャは引数の型、戻り値の型を指定する必要がない(指定してもよい)

クロージャを保持する構造体(メモ化または遅延評価)

struct Cacher<T>
    where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}
  • Fnトレイトを使用し、トレイト境界を指定(Fn(u32) -> u32
    u32型の引数を1つ取り、u32型の戻り値を返す
    全てのクロージャは、Fn、FnMut、または FnOnceのいずれかのトレイトを実装
    • Fn 環境から値を不変で使用
    • FnMut 可変で値を使用(環境を変更することができる)
    • FnOnce 環境からキャプチャした変数の所有権を奪い自身にムーブ

標準ライブラリ

文字処理

String型

  • 伸長可能、可変、所有権のあるUTF-8エンコードされた文字列
  • Vec<u8>のラッパー
  • 標準ライブラリには他の文字列型(OsString、OsStr、CString、CStrなど)あり
  • 空の文字列を生成
    let mut s = String::new();
  • 文字列リテラルからStringを生成するには、
    • to_string()メソッドを使う
      let s = "initial contents".to_string();
    • from関数を使う
      let s = String::from("initial contents");
  • 文字列に追記
    s.push_str("bar");
  • 文字列の走査
    for c in s.chars() { ... }
  • 文字列の結合、+演算子
    let s3 = s1 + &s2; s1はムーブされる
  • 文字列の結合、format!マクロ
    let s = format!("{}-{}-{}", s1, s2, s3);

Option型

Rustにはnullがないので、値が存在するかしないかの概念をコード化する enum で定義したOption<T>型が提供されている。

enum Option<T> {
    Some(T),
    None,
}
  • Optional型、および列挙子のSomeとNoneは preludeに含まれる
    let some_number = Some(5); のようにOptionスコープなしに記述可能

Optionとmatchフロー制御

一連のパターンに対して値を比較し、マッチしたパターンに応じてコードを実行する match フロー制御演算子を使うと便利。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

コレクション

主なコレクション

  • ベクタ型(Vec<T>)
  • ハッシュマップ

ベクタ型

  • 空のベクタを生成
    let v: Vec<i32> = Vec::new();
  • 要素を指定してベクタを生成
    let v = vec![1, 2, 3];
  • ベクタへ要素を追加
    v.push(4); 末尾に値4を追加
  • ベクタの要素を取得(添字記法とgetメソッドがある)
    let third: &i32 = &v[2];
    let third: Option<&i32> = v.get(2);
    • ベクタの要素を超えるインデックスを指定すると、添字記法はパニックが生じ、getメソッドはNoneが返る
  • ベクタの要素を走査するときは
    for i in &v { println!("{}", i); }
  • ベクタの要素を更新
    for i in &mut v { *i += 50; }

ハッシュマップ

  • 空のハッシュマップを生成
    let mut scores = HashMap::new();
  • ハッシュマップにキーと値を挿入
    scores.insert(String::from("Blue"), 10);
  • キーを指定して値を取り出し
let team_name = String::from("Blue");
let score = scores.get(&team_name);
  • キーと値の走査

スマートポインタ

色々なスマートポインタが標準ライブラリに定義されている。
参照と違い、多くのスマートポインタは指しているデータを所有する。
通常構造体で定義され、DerefとDropトレイトを実装している。

内容
Box<T> ヒープに値を確保
Rc<T> 複数の所有権を可能にする参照カウント
Ref<T> 実行時に借用規則を強制する
RefMut<T>

Box<T>

スタックではなくヒープにデータを格納し、スタックにはヒープデータへのポインタが残る。
let b = Box::new(5); i32の値をヒープに格納

i32はデフォルトでスタックに値が置かれる。

雑多メモ

  • classがない!
    structまたはenumでユーザー型を定義、メソッドをユーザー型に紐づける
  • 例外がない
    回復可能なエラーは戻り値で通知。Result型(enum)で関数呼出しの成否と戻り値を返す
    回復不能なエラーはpanic(プログラムの実行中止)
  • 継承がない
    ポリモーフィズムは trait を使う
    コードの再利用は trait にメソッドのデフォルト実装
  • nullがない
  • 変数はデフォルトで不変
  • 変数がスコープを外れると束縛している値を破棄
  • 変数を代入すると所有権が移動(Copyトレイトの注釈がない型の場合)
    • 所有権のない変数へのアクセスはコンパイルエラー
    • 所有権を移動せずに参照することは可(借用)
    • ディープコピーは、cloneメソッド
  • タプル型がある
  • ジェネリックな型がある
  • クロージャがある
  • スマートポインタがある
  • 標準でビルド&パッケージ管理システム(Cargo)がある *