Rust本を読んだので,学んだこと諸々を書いておく

若干理解が足りない部分もあるが,個人的に面白かった項目を書いておく.

参照周り

Rustは値ごとに所有権を有しており,その扱い方がいろいろある.

移動

所有権を移動した値を再度使おうとするとエラーが出る. メモリ上の動きとしては,sのヘッダ情報には {ヒープ領域へのアドレス,capacity, length} が入っており,このヘッダ情報(3ワードのみ)をtに移動する.

下は移動された後の変数を使おうとするとエラーを吐く例.

fn main() {
    let s = vec![1, 2, 3];
    let t = s;
    println!("{:?}", t);
    println!("{:?}", s);
}

// error
3 |     let t = s;
  |         - value moved here
4 |     println!("{:?}", t);
5 |     println!("{:?}", s);
  |                      ^ value used here after move

関数の引数として移動する場合もエラーを吐く.

fn print_vec(v: Vec<i64>) {
    for val in v {
        println!("{}", val);
    }
}

fn main() {
    let v = vec![1, 2, 3];
    print_vec(v);
    println!("{:?}", v);
}

// error
9  |     print_vec(v);
   |               - value moved here
10 |     println!("{:?}", v);
   |                      ^ value used here after move

借用

参照を渡すことで,所有権を借用できる. 所有権は一時的に貸し出され,その後帰ってくる. なので以下のコードはエラーを吐かない.

fn print_vec(v: &Vec<i64>) {
    for val in v {
        println!("{}", val);
    }
}

fn main() {
    let v = vec![1, 2, 3];
    print_vec(&v);
    println!("{:?}", v);
}

// output
1
2
3
[1, 2, 3]

Copy型

Copy型は移動した場合,Stack領域に値のコピーが作成される.

下のコードは,Copy型であるi32を移動させた時の例. 基本的にヒープ領域にある値を参照している型は,Copy型ではないので,移動した時にコピーは作成されない.

fn main() {
    let s: i32 = 1;
    let t: i32 = s;
    println!("t: {}", t);
    println!("s: {}", s);
}

// output
t: 1
s: 1

Box

T型の変数を受け取りそれをヒープ領域に写してその参照を得る. Boxの変数の参照が切れると,内部の参照がもつ領域は解放される.

以下のexampleがわかりやすい.

Box, stack and heap - Rust By Example - Rust Documentation

エラー周り

Result型

Eitherみたいなアレみたいな感じ. 値を取り出すときはパターンマッチで,OkかErrを判定して値を取り出す. ?演算子は,その関数でエラーが起こったらErrを返し,正常に関数が実行できたら次の行へと処理を移せる最高のやつ. 難しいことを考えず,手続き型のように書けるのがいいですね.

use std::io::prelude::*;
use std::fs::File;
use std::io::Result;

fn read_file(filename: String) -> Result<String> {
    let mut file = File::open(filename)?; // => Err?
    let mut contents = String::new();
    file.read_to_string(&mut contents)?; // => Err?
    return Ok(contents);
}

fn main() {
    let contents = read_file("foo.txt".to_string());
    match contents {
        Ok(v) => println!("{}", v),
        Err(e) => panic!(e),
    }
}

// foo.txt
1
2
3

// output
1
2
3

Option

SomeとNoneでパターンマッチして,分岐したりできるいろんな言語でよくあるやつ.

fn div(a: i64, b: i64) -> Option<i64> {
    if b == 0 {
        return None;
    }
    return Some(a / b);
}

fn main() {
    let val = div(10, 5); // => 10 / 5
    match val {
        Some(v) => println!("{}", v),
        None => println!("None value"),
    }
    let none_val = div(10, 0); // => 10 / 0
    match none_val {
        Some(v) => println!("{}", v),
        None => println!("None value"),
    }
}

// output
2
None value

パターンマッチ

Rustには,パターンマッチがあるのでいろいろ分岐が可能. listパターンみたいなものはないっぽい?

下のexampleがわかりやすくて良いです.

All the Pattern Syntax - The Rust Programming Language

トレイト

みんな大好き型クラス的なアレみたいな感じ. impl <trait> for <struct name>とかで実装する.

use std::ops::Add;

struct User {
    name: String,
}

impl Add for User { // impl Add trait
    type Output = Self;
    fn add(self, other: Self) -> Self {
        return User{name: self.name + "_" + &other.name};
    }
}

fn main() {
    let mituba = User{name: "mituba".to_string()};
    let mother = User{name: "mother".to_string()};
    let mituba_mother = mituba.add(mother); // mituba + _ + mother
    println!("{}", mituba_mother.name);

    let mituba = User{name: "mituba".to_string()};
    let father = User{name: "father".to_string()};
    let mituba_father = mituba + father; // こうも書ける
    println!("{}", mituba_father.name);
}

// output
mituba_mother
mituba_father

PartialEqを実装して,assert_eqとかできるようにする.

use std::cmp::PartialEq;

#[derive(Debug)]
struct User {
    name: String,
}

impl PartialEq for User {
    fn eq(&self, other: &User) -> bool {
        self.name == other.name
    }
}

fn main() {
    let mituba = User{name: "mituba".to_string()};
    let mother = User{name: "mother".to_string()};
    assert_ne!(mituba, mother);

    let mituba = User{name: "mituba".to_string()};
    let mituba2 = User{name: "mituba".to_string()};
    assert_eq!(mituba, mituba2);
}

下のドキュメントにポリモーフィズムぽいやつも記載されている.

Advanced Traits - The Rust Programming Language

クロージャ

関数っぽく書けたり,map関数などに渡せる高階関数的なアレな感じ.

fn main() {
    let cl = |x: i64| -> i64 x + 1;
    println!("{}", cl(1));

    let cl2 = |x: i64| -> i64 {
        let x1 = x + 1;
        return x1 + 2;
    };
    println!("{}", cl2(1));
}

// output
2
4

mapにクロージャを食わせる

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    v.iter().map(|x| x + 1).for_each(|x| println!("{}", x));
}

// output
2
3
4
5
6

移動(クロージャ)

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let cl = |vc: &Vec<i64>| vc.iter().for_each(|x| println!("{}", x));
    cl(&v);
    println!("{:?}", v);
}

// output
1
2
3
4
5
[1, 2, 3, 4, 5]

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let cl = |vc: Vec<i64>| vc.iter().for_each(|x| println!("{}", x));
    cl(v);
    println!("{:?}", v);
}

// output
9  |     cl(v);
   |        - value moved here
10 |     println!("{:?}", v);
   |                      ^ value used here after move

下のドキュメントに他の事柄はいろいろ載ってます.

Closures: Anonymous Functions that Can Capture Their Environment - The Rust Programming Language

まとめ

Rustは,今までのプログラミング言語には若干ない概念があったりして,少しとっかかりづらい面もある感じですが,やってみると意外と面白かったりするので是非. お堅い言語かと思いきや意外と柔軟だったり.

本を読んだ後に参考にしたドキュメントです.ドキュメントが本当にしっかりしているのに感動したりして最高.

Foreword - The Rust Programming Language