Open10

Rust 事始

jnstjnst

Directory structure

src
├── hello_world.rs
├── lib.rs
└── main.rs
  • Rust コンパイラの開始点をクレートルート (crate root) という
  • lib.rs は特殊なファイルで、存在する場合はライブラリパッケージとして扱われ、クレートルートとなる
  • lib.rs に pub mod hello_world; と書けば、hello_world.rs ファイルがモジュールとして扱われ、main.rs から参照できる

Code

hello_world.rs
pub fn hello() {
    println!("{0}, {1}! {0}! {0}!", "hello", "world");
}
lib.rs
pub mod hello_world;
main.rs
mod hello_world;

fn main() {
    crate::hello_world::hello();
}
実行結果
hello, world! hello! hello!
jnstjnst

単体テスト

実装コードと同じファイルに test 用の注釈をいれて書く

pub fn add(x: i32, y: i32) -> i32 {
    x + y
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_eq!(add(10, -1), 9);
    }    
}

Rust Playground でのテスト実行

Rust Playground でテストを動かす場合は、上記のコードでもそのまま動くが、下記のようにモジュールなしでミニマムに書いても良い

pub fn add(x: i32, y: i32) -> i32 {
    x + y
}

#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);
    assert_eq!(add(10, -1), 9);
}
jnstjnst

配列と For 文

実用上 Java では List、JavaScript と Ruby では配列、Go では Slice を使うが、Rust では std::vec::Vec を使う
語源は理解しつつも省略して Vec と定義されているから何気に「ベク」と呼んでしまいそうだが「ベクタと発音する」と公式ドキュメントに明記されている

A contiguous growable array type, written as Vec<T> and pronounced ‘vector’.

使い方

初期化して push して remove

fn main() {
    let mut v = Vec::new();
    v.push(10);
    v.push(20);
    v.push(30);
    println!("{:?}", v);

    v.remove(1);
    println!("{:?}", v);
}
実行結果
[10, 20, 30]
[10, 30]
  • mut で宣言しないと変数を変更できない
    • JavaScript は const で宣言すると変数の上書きは不可だが中身は変更可
  • std ライブラリの型は {:?} 指定で出力可能

リテラルで初期化したい場合のためにマクロが用意されている

let nums = vec![10, 20, 30];

For 文は 1 種類しかない

Go だとスライスを最初から最後まで走査する場合は

func main() {
	var nums = []int{1, 2, 3, 5, 7}
	for i, v := range nums {
		fmt.Printf("index: %v, value: %v\n", i, v)
	}
}

任意にループさせたい場合は C 言語由来の

func main() {
	var nums = []int{1, 2, 3, 5, 7}
	for i := 0; i < len(nums); i++ {
		fmt.Printf("index: %v, value: %v\n", i, nums[i])
	}
}

という使い分けができるが Rust には前者しかないのでびっくりした

fn main() {
    let nums = vec![1, 2, 3, 5, 7];
    for n in nums {
        println!("{}", n);
    }
}

インデックスが欲しい場合は

fn main() {
    let nums = vec![1, 2, 3, 5, 7];
    for (i, v) in nums.iter().enumerate() {
        println!("index: {}, value: {}", i, v);
    }
}

どちらの書き方も Vec のインデックスを一巡するので融通が利かないが、以下のように Range を使う書き方なら開始位置をずらしたりできる

fn main() {
    let nums = vec![1, 2, 3, 5, 7];
    for i in 1..nums.len() {
        println!("index: {}, value: {}", i, nums[i]);
    }
}
jnstjnst

Rust 自体のアップグレード

$ rustc -V
rustc 1.52.1 (9bc8c42bb 2021-05-09)
$ rustup update

info: syncing channel updates for 'stable-x86_64-apple-darwin'
info: latest update on 2021-06-17, rust version 1.53.0 (53cb7b09b 2021-06-17)
info: downloading component 'rust-src'
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 16.1 MiB /  16.1 MiB (100 %)   7.5 MiB/s in  2s ETA:  0s
info: downloading component 'rust-std'
 24.4 MiB /  24.4 MiB (100 %)   7.7 MiB/s in  3s ETA:  0s
info: downloading component 'rustc'
 58.6 MiB /  58.6 MiB (100 %)   5.9 MiB/s in 16s ETA:  0s
info: downloading component 'rustfmt'
info: removing previous version of component 'rust-src'
info: removing previous version of component 'cargo'
info: removing previous version of component 'clippy'
info: removing previous version of component 'rust-docs'
info: removing previous version of component 'rust-std'
info: removing previous version of component 'rustc'
info: removing previous version of component 'rustfmt'
info: installing component 'rust-src'
info: using up to 500.0 MiB of RAM to unpack components
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 16.1 MiB /  16.1 MiB (100 %)   3.8 MiB/s in  3s ETA:  0s
info: installing component 'rust-std'
 24.4 MiB /  24.4 MiB (100 %)   7.7 MiB/s in  3s ETA:  0s
info: installing component 'rustc'
 58.6 MiB /  58.6 MiB (100 %)   9.3 MiB/s in  6s ETA:  0s
info: installing component 'rustfmt'

  stable-x86_64-apple-darwin updated - rustc 1.53.0 (53cb7b09b 2021-06-17) (from rustc 1.52.1 (9bc8c42bb 2021-05-09))

info: cleaning up downloads & tmp directories
$ rustc -V
rustc 1.53.0 (53cb7b09b 2021-06-17)
jnstjnst

コピートレイト

再代入することで変数 a の束縛は解除されるが、もし代入したオブジェクトがコピートレイトを実装しているならコピーが作成されるので変数 a の束縛もそのままらしい。

let a = object;
let mut b = a;

オブジェクトが String 型の場合、String 型はコピートレイトを実装していないから変数 a の束縛は解除される。つまりプログラマはどの型がコピートレイトを実装しているか知っているべきで、そういった経験知が必要になってくるということ?

jnstjnst

関数使ってるのに使ってないよって警告がでるとき

他から参照されてないけど関数は定義しておきたいような場合 #[allow(dead_code)] を付けて警告を抑制できる

rectangle.rs
#[derive(Debug)]
pub struct Rectangle {
    pub width: u32,
    pub height: u32,
}

impl Rectangle {
    #[allow(dead_code)]  // <-- これ
    pub fn area(&self) -> u32 {
        self.width * self.height
    }
}

けれど、単体テストで使われていたり、他コードから参照されているのに associated function is never used の警告がでる場合がある

$ cargo test
   Compiling x-rust v0.1.0 (/Users/jnst/go/github.com/jnst/x-rust)
warning: associated function is never used: `area`
 --> src/introduction.rs:8:12
  |
8 |     pub fn area(&self) -> u32 {
  |            ^^^^
  |
  = note: `#[warn(dead_code)]` on by default

公開してないとこうなってしまうようだ
今回のケースだと下記のように関数には pub を付けていたが

rectangle.rs
pub fn area(width: u32, height: u32) -> u32 {
    width * height
}

関数の宣言だけでなくモジュール宣言でも公開する必要がある(なんでだろう?)

lib.rs
pub mod rectangle;

#[allow(dead_code)] を付ける必要はもちろんない

jnstjnst

用語

インスタンス

Rust は関数型言語のパラダイムが強い
Java 等オブジェクト指向の言語がクラスの実体をインスタンスと呼ぶのとは違い、トレイトを実装した構造体のことをを インスタンス と呼ぶ

マーカートレイト

メソッドを持たないトレイトのこと

jnstjnst

クロージャ

  • トレイトである
  • 動的 Size 型である
  • 参照や Box を使うことでオブジェクトとして扱える
  • トレイトオブジェクト という
let add = |x: u32, y: u32| -> u32 {x + y};
jnstjnst

トレイトの規定実装

#[derive(Debug)] のように記述するだけで実装される規定実装があるものと、#[derive(Display)] のようにプログラマーが実装しなければならないトレイトがある