Rust Memo

Rust 本家

Rust本家

Rustプログラミング言語

ドキュメント

Rustを学ぶ - Rustプログラミング言語

標準ライブラリのリファレンス

std - Rust

Playground

Rust Playground

Rust By Example (RBE)

Introduction - Rust By Example

.

この記事は主に RBE をベースにしている。

Rustインストール後は rustup doc でローカルのドキュメントをブラウザで表示できる。

Hello World など

Hello World

fn main() {
    pirntln!("Hello World!");
}

FizzBuzz

fn main() {
    fizz_buzz();
}

fn fizz_buzz() {
    for i in 1..=100 {
        let mut s = "";
        if i % 15 == 0 {
            s = "FizzBuzz";
        }
        else if i % 3 == 0 {
            s = "Fizz"
        }
        else if i % 5 == 0 {
            s = "Buzz"
        }
        if (s.len() != 0) {
            println!("{}: {}", i, s);
        }
    }
}

Rustc

rustc は、Rust のコンパイラ
通常は直接使わず、 cargo 経由で実行される。

Cargo

cargo (カーゴ)は Rust のプログラムをビルドするツール。
Rust の外部パッケージ(crate (クレート)と呼ばれる)のパッケージマネージャでもある。
crate は https://crates.io で公開されている。
Cargo.toml に、追加で必要な crate を記述しておくと cargo はビルドに先立ち crate.io からその crate をダウンロードする。
(※ 自分で書くプログラムも crate と言う)

$ cargo new my_app             # 作業環境の作成
$ cargo new --lib my_lib       # ライブラリ作成用作業環境の作成
$ cd my_app
$ cargo build                  # デバッグビルド ==> target/debug/my_app ができる。
$ cargo build --release        # リリースビルド ==> target/release/my_app ができる
$ cargo check                  # ビルドせず、コードのチェックだけしてビルドはしない。
$ cargo run                    # 実行 (./target/debug/my_app でも実行できる)
$ cargo doc                    # target/doc にドキュメントを生成
$ cargo -h                     # cargo の使い方
$ cargo build -h               # cargo build の使い方

コメント

コメント

// ラインコメント
/* ブロックコメント */
/*
 * ブロックコメント
 */

Doc Comment

/// で Doc コメントを書くことができる。 関数や、構造体に Doc コメントを書いておくと、cargo doc で生成されるドキュメントにその記述が反映される。

生成される場所: target/doc/my_app/index.html

Doc コメント内では Markdown がサポートされる。

Documentation - Rust By Example

/// 四角形を表す構造体
struct Rectangle {
    left:   u32,
    top:    u32,
    width:  u32,
    height: u32,
}

文字列のフォーマットとプリント

Rust では、コンソールへの出力や、文字列のフォーマットは println! や format! といったマクロを使う。(! がつくものはマクロ)

Rust の関数は現状可変個引数に対応してないのでマクロでやるしかない。

format!

String 型の文字列にフォーマット結果を書き込んで返す

print!

format! と同じだけど、標準出力にプリントする

println!

print! と同じだけど、改行も出力する

eprint!

標準エラー出力にプリントする

eprintln!

eprint! と同じだけど、改行も出力

fn fmt() {
    let s = format!("{}", "Hello");
    print!("{}\n", s);
    println!("{} World", "Hello");
    println!("{} + {} = {}", 1, 2, 3);
    println!("My name is {0}, {1} {0}.", "Bond", "James");
    println!("My name is {bond}, {james} {bond}.", bond="Bond", james="James");
    println!("dec: {num}, oct: {num:o}, hex: {num:x}, bin: {num:b}", num=15);
    println!("{:?}", ("Hello", "World"));

    #[derive(Debug)]
    struct St(i32);
    println!("{:?}", St(32));
    println!("{:#?}", St(32));
}
Hello
Hello World
1 + 2 = 3
My name is Bond, James Bond.
My name is Bond, James Bond.
dec: 15, oct: 17, hex: f, bin: 1111
("Hello", "World")
St(32)
St(
    32,
)

Format 記法いろいろ

"{:?}"デバッグ用のフォーマット指定。

"{:#?}" も同じだが、構造体のメンバなど、見やすく表示してくれる(Pretty Print)

デバッグ用のフォーマットは、デフォルトでは、標準ライブラリ std が提供する型にしか対応していない。

struct や enum で作るユーザー定義型は fmt::Debug トレイトを実装しないといけないのだが #[derive(Debug)] とすることで自動的に実装を提供できる。

(必要な時に自動的にやってくれるようになるとよいのだけど)

"{}" に対する有効なテキストは fmt::Display トレイトを自分で実装する必要がある。

Debug

Display

プリミティブ

スカラー

符号付き整数
i8
i16
i32
i64
i128
isize           /// ポインタのバイト数ぶんの幅をもつ

小数点以下がない数値はデフォルトで i32 になる。

符号無し整数
u8
u16
u32
u64
u128
usize           /// ポインタのバイト数ぶんの幅をもつ
浮動小数点数
f32
f64

小数点以下を含む数値はデフォルトで f64 になる

文字型(Unicode
char
bool型
bool
ユニット型
()

空のタプルだけがユニット型になれる。何もないことを表すのに使われる。

複合型

配列
[1, 2, 3]

配列は同じ型のデータを複数保持する

タプル
(1, true)

タプルは異なる型のデータを複数保持する

変数に使う型

変数を宣言するとき、: <型名> という形で、変数名の後ろに型を指定してもよい。指定しなくても多くの場合は型推論(inference)が働いて自動的に型が決定される。

fn variable() {
    // 型を指定してもよい
    let result: bool = true;

    // なくてもよい(true を代入しているので bool 型とわかる)
    let result = true;

    // 数値はデフォルトで i32
    let num = 15;

    // 小数点付きの数値はデフォルトで f64
    let float = 100.0;

    // 型サフィックス付き数値をいれるとその型になる。
    let unsigned = 15u32;

    // 型推論システムが見ているのは初期値だけではない。
    let mut var = 100;               // i32 型のように思えるが、この段階ではまだ型は未確定で…
    var = 1_000_000_000u64;          // ここで u64型が確定する。

    // 下記のコードはエラー。型は途中で変更できない。
    // var = true;

    // でも、同じ名前の変数を新たに導入して以降のコードで古い var を見えなくする
    // こと(シャドーイング)で型を変更したように見せかけることは可能。
    let var = true;
}

リテラル (Literals)

Literals and operators - Rust By Example

タプル (Tuples)

Tuples - Rust By Example

配列とスライス (Arrays and Slices)

Arrays and Slices - Rust By Example

カスタムタイプ (Custom Types)

Custom Types - Rust By Example

カスタムタイプは 2つ。struct と enum

構造体 (Struct)

Structures - Rust By Example

列挙型 (Enum)

Enums - Rust By Example

use宣言

use - Rust By Example

Cのような Enum も可能

C-like - Rust By Example

Enum を使ったリンクトリスト

Testcase: linked-list - Rust By Example

定数 (constants)

constants - Rust By Example

変数の束縛 (Variable Bindings)

Variable Bindings - Rust By Example

let

変数はデフォルトでは書き換え不可 (Mutability)

Mutability - Rust By Example

スコープとシャドーイング (Scope and Shadowing)

Scope and Shadowing - Rust By Example

初期化の遅延 (Declare first)

Declare first - Rust By Example

滅多に使わない

初期化忘れのコンパイルエラーになりやすい

変数を一時的に不変にする (Freezing)

型 (Types)

Types - Rust By Example

  • 組み込み型を別の組み込み型に変換する (Casting)
  • リテラルで型を指定
  • 型推論 (Type Inference)
  • 型に別名をつける (Aliasing)

組み込み型を別の組み込み型に変換する (Casting)

Casting - Rust By Example

リテラルで型を指定 (Literals)

Literals - Rust By Example

数値リテラルは、接尾辞として型を指定することで、型を明示することができる。 これをアノテーション(annotation:注釈) と呼んでいる様子。

let a = 255u8;
let b = 0x100u16;

アノテーションがない場合、その変数の使い方をもとに型推論によって型が決まる。
なんら制約がない場合は、整数は i32 、浮動小数点数は f64 になる。

型推論 (Inference)

Inference - Rust By Example

Rust の型推論エンジンはかなり頭がよいので、初期化コードだけでなく、初期化後の変数の使い方も見て型を決定する。

// 型アノテーションによって elem が u8 であることはこの場で確定。
let elem = 5u8;

// 空のベクタ(可変長の配列)
let mut vec = Vec::new();
// この時点では、まだベクタに格納される型は未確定なので、vec の型も未確定。
// コンパイラは、なんからのベクタであることだけを知っている。(Vec<_>)

// ベクタに最初の要素をセット
vec.push(elem);
// ここで初めてベクタの型が確定する。(Vec<u8>)

型に別名をつける (Aliasing)

Aliasing - Rust By Example

  • type で型に別名をつけることができる。
  • 型は UpperCamelCase でないとウォーニングが出る。
  • #[allow(non_camel_case_types)] というアトリビュートを指定すると、キャメルケース以外でもOK
type UInt = u32;

#[allow(non_camel_case_types)]
type uint32_t = u32;

type の目的は、ボイラープレート (boilerplate) なコードを減らすため。
ボイラープレートとは、言語仕様からくる要請によって、プログラマが繰り返し書かされる同じコードのこと。
ジェネリックを使うと、型がどんどん複雑になるので短い名前をつける機能は必須になってくる。

アトリビュート (Attribute: 属性)

Attributes - Rust By Example

アトリビュートは、クレート、モジュール、アイテム(関数や構造体など)につけるメタデータ(追加情報)。

下記の用途で使う。

  • 条件付きコンパイル
  • クレートの名前、バージョン、タイプ(binary or library)を指定する
  • ウォーニング(lints)の抑止
  • コンパイラの機能(マクロ、globインポートなど)を有効にする
  • 外部ライブラリとのリンク
  • 関数を Unit テスト用とマークする
  • 関数に、ベンチマークの一部になるもの、とマークする

クレート全体に適用する場合はファイルの先頭などで #! をつけて指定する。(#! は Inner attributes)

#![cfg(...)]

モジュールやアイテム(関数や構造体など)につける場合は ! をつけずアイテムの前に置く。(# は Outer attributes)

#[cfg(...)]
fn func() {}

アトリビュートには、いくつかの書き方で設定値を指定できる

#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]

アトリビュートは複数の値を持つこともできる。

#[attribute(For, Bar, Buz)]

複数行に渡って記述することも可能。

#[attribute(value0, value1, value2, value3,
            value4, value5, value6, value7)]

アトリビュートの詳細はこちら
Attributes - The Rust Reference

使わないコードへの warning を抑止するアトリビュート(dead_code)

dead_code - Rust By Example

コンパイラは使われていない関数には unused function とウォーニングを出す。 下記のアトリビュートを指定することで抑止できる。(ただ、現実の開発では使われていない関数は削除するほうがよい)

#[alllow(dead_code)]
fn unused_func() {
}

クレート全体に適用するには ! 付きで書いておく

#![allow(unused)]

TBD: あれ dead_code と unused の違いは?

クレートにつけるアトリビュート

Crates - Rust By Example

crate_type アトリビュートでクレートがバイナリクレートか、ライブラリクレートを指定する。 ライブラリのタイプも指定できる。(TBD: それなに?)

#![crate_type = "lib"]

crate_name アトリビュートでクレートの名前を指定する。

#![crate_name = "RingBuffer"]

ただし Cargo を使う Rust プロジェクトでは crate_type, crate_name は意味を持たない。(Cargo.toml で管理するためかな?)

たいていの Rust プロジェクトとは Cargo を使って管理するので、これらのアトリビュートはほとんど使われない様子。

crate_type アトリビュートを使うときは rustc--crate-type オプションは必要ない。

$ rustc mylib.rs
$ ls lib*
libmylib.rlib

crate_type アトリビュートがない場合 rustc がエラーになる

$ rustc mylib.rs
error[E0601]: `main` function not found in crate `mylib`
 --> mylib.rs:3:1
  |
3 | / pub fn hello_mylib() {
4 | |     println!("mylib");
5 | | }
  | |_^ consider adding a `main` function to `mylib.rs`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0601`.

cfg アトリビュート

cfg - Rust By Example

cfg アトリビュートで、条件コンパイルができる。

条件付きコンパイルを制御する方法は cfg アトリビュートのほか、cfg_attr アトリビュートと cfg! マクロがある。

cfg アトリビュートによる条件コンパイル

通常は、条件コンパイルはコードの可読性を下げるのでアプリケーションを書くときは使わないように書くのが正しい。

が、HWリソースを節約したいときなどには、動かさないコードは実行バイナリに含めたくないので必ず必要になる機能。

#[cfg(target_os = "linux")]
fn who_am_i() {
    println!("Linux");
}

#[cfg(target_os = "windows")]
fn who_am_i() {
    println!("Windows");
}

/// any: foo または bar が定義されている場合コンパイルされる
#[cfg(any(foo, bar))]
fn needs_foo_or_bar() {
}

/// all: unix ファミリー、かつ、32bitアーキテクチャの場合のみ
#[cfg(all(unix, target_pointer_width = "32"))]
fn memcpy4byte() {
}

/// not: foo が定義されていないこと。
#[cfg(not(foo))]
fn need_not_foo() {
}

cfg_attr アトリビュート

cfg_attr アトリビュートは、第一引数が真なら、第二引数以降をそれぞれ '#[' と']' で囲んでアトリビュートを作り、自らをそのアトリビュートで置き換える。

うーん。うまい使い方が思いつかない。

cfg!() マクロによる条件コンパイル

fn main() {
    cond_print();
}

fn cond_print() {
    let endian = if cfg!(target_endian = "big") {
        "Big Endian"
    }
    else if cfg!(target_endian = "little") {
        "Little Endian"
    }
    else {
        "??"
    };
    println!("{}", endian);   
}

cfg アトリビュートで任意のオプション(Configration Option)を使う方法

rustc--cfg オプションで任意の値を渡すことが可能。

$ rustc --cfg 'verbose' --cfg 'my_option="foo"'

この例では、コード中のアトリビュート #[cfg(verbose)]#[cfg(my_option="foo") が有効になる。

feature オプションは Cargo で予約

feature オプションは Cargo によって設定される Configuration Option なので、使わない方がよさそう。

Cargo.toml で [features] を設定する方法はこちら
Features - The Cargo Book

組み込みの cfg オプション

  • target_arch
  • target_feature
  • target_os
  • target_family
  • unix
  • windows
  • target_env
  • target_endian
  • target_pointer_width
  • target_vendor
  • test
  • debug_assertions
  • proc_macro

詳細はこちら
Conditional compilation - The Rust Reference

モジュール