81
82

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.

stackoverflowのアンケート結果で好きな言語3位になったRustがまもなくリリースするらしいので入門してみた

Last updated at Posted at 2015-04-26

はじめに

stackoverflowが毎年行っている開発者向けアンケートの2015年の結果で好きな言語(ここ1年使ってきて、今後も使い続けたい言語)で3位になったRustについて名前くらいしか知らなかったので入門してみました。

追記

最初はbeta時点の情報でしたが、Stable版リリースに伴って記事更新しました。
といっても動作確認し直したくらいです。
あと、最初に書いてたstackoverflowのアンケート結果紹介はやっぱり邪魔だったので消しましたw
私のどうでもよい コメントは更新履歴から参照してもらえればと思います。

Rustとは

とりあえず箇条書きで。
(なおrustは始めたばかりなので自分の理解がおかしい点もあるかもしれません。おかしな点に気づいた方は マサカリ ご指摘頂けたらと思います。)

  • 背景とマイルストーン
  • 2009年にGraydon Hoareによって開発されたもので、2010年にMozilla Researchからアナウンス。
  • バージョン1.0が2015年5月15日に出たばかり。
  • オフィシャル情報
  • 公式サイトには"Rust is a systems programming language that runs blazingly fast, prevents almost all crashes, and eliminates data races. "とか書いてあっていきなり鼻血が出そうになる。
    • safety, speed, and concurrencyの3つにフォーカスしている模様
    • 低レイヤーのシステム言語
  • オフィシャルガイドはかなり充実している。
    • ベータ時点では章があるだけで"Coming Soon"になっているものも多かった。
  • gitのレポジトリはこちら
  • ライセンスはApache License2.0とMIT Licenseのデュアルライセンス
  • 日本語情報
  • 日本語で手に入る情報はまだまだ少ないようだが、好意的な記事が多い印象。
  • 実は2013年からAdvent Calendarもある。
    • ただし、結構空いている。
    • そして2014年のAdvent Calendarには無い。
  • Rust Samuraiなる勉強会が何度か開催されている模様。
  • 言語とか書き方とかの特徴ぽいもの
  • コンパイル言語
  • なるべくヒープよりもスタックに積む。
  • 静的に型付けされるが、型宣言は必須ではない。
  • 変数はデフォルトではimmutableでmutキーワードを付けるとmutableになる。
  • 使われていない変数は警告するけどコンパイルエラーにはならない。
  • 初期化されてない変数へのアクセスはコンパイルエラーになる。
  • ポインタに似たリファレンスという機能があるが、ポインタと違ってコンパイル時に安全にチェックされる。
  • メモリのallocate/deallocateはrustが面倒を見てくれるがGCは無い。(reference counterはあるので、これをGCと呼ぶかは意見が分かれる気がする。)
  • 拡張子は.rs
  • ファイル名等はlower_snake_case
  • インデントはタブじゃなくてスペース4個が推奨。
  • rust特有っぽいものとしてはunsafe, macro, ownership/borrowing/lifetime等がある。(詳しくは後述)
  • 他の言語にもありそうなものとしては、struct, enum, trait, closure等がある。(詳しくは後述)
  • 関数型プログラミング的な書き方もOOP的な書き方もできる。
  • エコシステム
  • crates.ioというcrate(ライブラリみたいなもの)のホスティングサイトがある。
    • ビルドファイル(tomlで書きます)に依存パッケージ書いてビルドコマンドを実行すれば依存性の解決は自動でできる。
  • IDEもある。(NeoVimのラッパーみたいですw)
  • vimプラグインもある。
  • その他
  • Rust使いはRustaceanと呼ぶらしい。けどRustyって呼んでるのも見かけた。
  • goと対比されることが多い(こことかこことかこことかこことかこことかこことかこことか)
  • rustだとググラビリティが低い(goほどではない)ので、rustlangで検索すると良いみたい。
  • 調べてたら割と情報が出てきて実は知らないのは自分だけだったらどーしようって思い始めてきたw

入門してみた

ここからはやってみた系のネタになります。
公式ドキュメントのこことかここを参考にしつつ、動かないところとかもあったので、ググったり試行錯誤したりしながらやってみた感じです。
なお、本記事は主にベータ時点で書いたので、ベータ時にオフィシャル情報が無かったものは本記事でも抜けてたりします。

install

公式ページでは以下の方法が勧められている。

install
curl -sf -L https://static.rust-lang.org/rustup.sh | sh

これはあんまり実行したくない。。。
が、ここにバイナリがあると書いてあるけど、結局install.shを実行することになるのでどっちでも良さそう。

動作確認

動作確認
$ rustc --version
rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)

こんな感じになればOK。
自分の場合(Fedora21)、以下をしないとダメだった。

.bash_profileとかに追記する
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib

ここで取り上げられているのでその内解決するはず。

uninstall

もしもアンインストールする場合は以下

アンインストール
sudo /usr/local/lib/rustlib/uninstall.sh

Hello World

適当にディレクトリを作って、以下の中身でmain.rsを作る。

main.rs
fn main() {
    println!("Hello world!");
}

コンパイル。

コンパイル
rustc main.rs

実行

実行
./main.rs
Hello World!

println!はrustマクロというらしい。
普通の関数だと!がつかない。
Cのマクロみたいに怖いものじゃないみたい。

cargo使って実行

(ここに書いた方法で手作りするのは普通じゃないっぽいので、読み飛ばしてもOKです)

上記のようにrustcでもコンパイルできるが、Cargoというツールもある。
Cargoを使うと、ビルドと依存パッケージのダウンロードとビルドができる。

設定ファイルはtomlで書く(ステキ!)。
設定ファイルの名前はCargo.tomlで、最初のCは大文字である必要がある。
cargoでビルドする場合、ディレクトリ構造が整っている必要がある。

helloworldの場合は、先ほど作ったmain.rsをsrcディレクトリの下に置けばOK。
helloworldをビルドする場合はこんな感じの内容でCargo.tomlを作成。

Cargo.toml
[package]

name = "helloworld"
version = "0.0.1"
authors = [ "hogehoge <hogehoge@example.com>" ]

現在はこんな感じ。

ディレクトリ構造
.
├── Cargo.toml
└─── src
     └── main.rs

そしてcargo buildを実行するとこんな感じのメッセージが表示されてビルドが完了する。

cargoでビルド
$ cargo build
   Compiling hello_world v0.0.1 (file:///home/hogehoge/work/rust/projects/helloworld)

ビルドすると、同じディレクトリにCargo.lockというファイルが作られる。
中には依存パッケージの情報等が書かれるようだが、今回は無いので以下のようにシンプルなものになる。
なお、このファイルは自分では触る必要は無さそう。

Cargo.lock
[root]
name = "helloworld"
version = "0.0.1"

実行はcargo runで行う。

cargoで実行
$ cargo run
     Running `target/debug/helloworld`
 Hello World!

実際はこんな感じで実行することもできるが非推奨。

手動実行
$ ./target/debug/helloworld
Hello World!

なお、cargo runはファイルの変更を加えた場合は自動でbuildが走る。

変更があった場合
$ cargo run
   Compiling hello_world v0.0.1 (file:///home/hogehoge/work/rust/projects/hellocargo)
      Running `target/debug/hello_world`

cargoでプロジェクト作成

上記のプロジェクト構造を自分で作ることは無いっぽくて、実際は以下のようなコマンドを実行すると推奨ディレクトリ構造でプロジェクトの雛形が出来上がる。

cargoでプロジェクト作成
$ cargo new hello_rust --bin

このコマンドで作った場合はCargo.tomlのAuthorはgitのglobalな設定から入れてくれるみたい。
--binはバイナリを作る場合で、ライブラリを作る場合は不要。--binを付けないとmain.rsの代わりにlib.rsが出来上がる。
この場合、buildすると実行ファイルじゃなくて.rlibファイルが出来上がる。
main.rsとlib.rsを併存させることは可能で一般的みたい。

基本文法

変数宣言

  • 変数はletで宣言する。
  • 型宣言は必須じゃない
  • 変数はデフォルトではimmutable。
  • mutableにする場合はmutを付ける。
  • ()で囲ってまとめて定義もできる
変数宣言
// 変数はletで宣言する
let a = 1;
// 型宣言は必須じゃないが明示もできる
let b :i32 = 2;
// デフォルトだとimmutableだけどmutをつけるとmutableになる
let mut c = 3;
// 複数宣言する場合は()をつける
let (d, e, f) = (4, 5, 6);

// 以下2つはmutじゃないからできない
//a += 1;
//b += 1;

// 以下はOK
c += 1;

関数

  • 戻り値は
  • ->で戻り値の型を書く
  • 関数の最後に;を付けずに書いたものが戻るが、returnで明示もできる

以下は同じ意味だけど、3つ目の書き方が推奨

return無しとreturn有り
fn foo(x: i32) -> i32 {
    if x < 0 { return x; }

    x + 1
}
return有り
fn foo(x: i32) -> i32 {
    if x < 0 { return x; }

    return x + 1;
}
return無し(推奨)
fn foo(x: i32) -> i32 {
    if x < 0 {
        x
    } else {
        x + 1
    }
}

戻り値が無い関数はdiverging functionと呼ぶ。
戻り値を書く場所に!(divergesと読む)を書く。

diverging_function
fn diverges() -> ! {
    panic!("This function never returns!");
}

変数に代入する式も書ける。(どんな型の変数でもOK)

divergesを代入
let i = diverges();

(戻り値無しの関数と同じだと思ってたんですが、コメントでご指摘頂いたので以下修正しました。)

戻り値無しの関数と同じものかと思っていたが、戻り値無しの関数は実際はunit type(()で表される)を返すものとのこと。
よって、例えば以下の場合、diverging functionだとiが0以上の場合には()を返すことになってしまいエラーになるが、

diverging_functionだとコンパイルエラーになる
fn mayreturn(i: i32) -> ! {
    if i < 0 {
        panic!("The argument is negative!");
    }
}

以下の戻り値無し関数の場合は正常に実行できる。

戻り値無し関数の場合はOK
fn mayreturn(i: i32) {
    if i < 0 {
        panic!("The argument is negative!");
    }
}

実際に後者を呼び出して戻り値を見てみると()が出力される。

戻り値の確認
let res = mayreturn(3);
println!("{:?}",res);// ()が出力される

プリミティブ型

  • boolean

    boolean
     let y: bool = false;
    
  • char
    4バイトのUnicode Scalar Value

    char
     let x = 'x';
     let fax = '℻';
     let hearts = '💕';
     let turn_a_gundam = '∀';
    
  • number
    型宣言しない場合は整数だとi32、浮動小数点数の場合はf64になる。unsignedはu32みたいに書く。i/uは8,16,32,64がある。fは32,64。
    マシンのポインタのサイズによって変わるisizeusizeというものもある。

  • 配列

    配列
     let a = [1, 2, 3];
     let b = [0; 10]; // 全部0で初期化される。mut付けないと意味なさそう
     // サイズ取得
     b.len();
     // インデックスアクセス
     b[0];
    
  • slice
    sliceは他のデータ構造への参照。

    slice
     let a = [0, 1, 2, 3, 4];
     let middle = &a[1..4];
    
  • str
    UTF-8。&strとStringがある。

  • &str

    string sliceと呼ばれている。文字列リテラルは&str。
    静的に割り当てられて、プログラム実行中に常に存在する。
    変更不可能。

  • String
    ヒープに割り当てられるもので変更可能。
    こんな感じで使う。

     ```rust:String
     let mut s = "Hello".to_string();
     s.push_str(", world.");
     ```
    
     逆にStringから&strにする場合は、`&`を使う。
    
     ```rust:Stringから&strの変換
     let ss = &s;
     ```
    
  • Tuple
    Tupleは以下のように宣言する

    Tuple
     let x = (1, "hello");
     let x: (i32, &str) = (1, "hello");
    

    実は複数の変数をできる以下の書き方も右辺はTupleでそれをdestructuring letして各変数に入れているみたい。

    複数の変数の宣言
     let (x, y, z) = (1, 2, 3);
    
  • function
    関数も型なので、こんな感じに書ける。

    関数
     fn foo(x: i32) -> i32 { x }
     let x: fn(i32) -> i32 = foo;
    

comment

//で始まる行はline comment。
//と実際のコメントの間にはスペースを一つ入れるのが推奨。

doc commentは///で始める。中ではマークダウンが使える(ステキ!)。
doc commentはrustdocコマンドを使うとhtmlに変換することができる。

if文

普通のif文は特に特徴はない。

if文
if x == 5 {
    println!("x is five!");
} else if x == 6 {
    println!("x is six!");
} else {
    println!("x is not five or six :(");
}

だけど、ifはexpressionなのでこんなこともできる。

if文使った代入
let y = if x == 5 { 10 } else { 15 };

ループ

  • for文はこんな感じ。あんまり特徴は無い。
    0から9が出力される。10は含まれない。

    for文
     for x in 0..10 {
     	println!("{}", x);
     }
    
  • while文はこんな感じ。特に特徴は無い。

    while文
     let mut x = 1;
     let mut done = false;
    
     while !done {
     	println!("{}", x);
     	x += 1;
     	if x % 10 == 0 {
     		done = true;
     	}
     }
    
  • 無限ループはwhile trueでも良いけど、loop文を使って以下のようにするのが推奨。

    loop文
     loop {
     	// 無限ループ処理
     }
    
  • iteratorを回すこともできる。for文の例をiteratorを回して書くと以下のようになる。
    なお、以下のmatchはswitch文みたいなもの。詳細は後述。

    iterator
     let mut range = 0..10;
     loop {
     	match range.next() {
     		Some(x) => {
     		println!("{}", x);
     		},
     		None => { break }
     	}
     }
    

    以下も内部的にiteratorを回している例。

    iteratorその2
     let a = [1,2,3];
    
     for i in &a {
     		println!("{}", i);
     }
    

    こんな書き方もできる。

    iteratorその3
     for i in a.iter() {
     		println!("{}", i);
     }
    

    iteratorに対するconsumerに当たる操作がある。

    collectの例
     let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
    

    その他iteratorに関しては詳しくはこちらを参照。

struct

他の言語と同じように使えるので基本形は省略。
メソッド生やすときはこんな感じでimplキーワードを使う。

Hogeというstructにadd_oneメソッドを生やす
struct Hoge {
    x: i32,
}

impl Hoge {
    fn add_one(&self) -> i32 {
        self.x + 1
    }
}

なお、

  • 複数のメソッドは同じimpl内に書ける。
  • &selfは参照を取るもの。
  • 他にもself,mut &selfという書き方ができ、それぞれ実際の値とmutableな参照を表しているがデフォルトでは&selfを使うべき。

メソッドチェーンにしたいときはstruct自体を返せばOK

メソッドチェーン
impl Hoge {
    fn add_two(&self) -> Hoge {
        Hoge{ x: self.x + 2 }
    }
    fn add(&self, value: i32) -> i32 {
        self.x + value
    }
}

// 呼び出し側
fn main() {
    let hoge = Hoge{x: 10};
    println!("{}",hoge.add_two().add(3));
}

static methodはこんな感じ。

staticメソッド
impl Hoge {
    fn new(x: i32) -> Hoge {
        Hoge{x: x}
    }
}

// 呼び出し側
fn main() {
    let hoge2 = Hoge::new(3);
}

builder pattern

rustにはメソッドのオーバーロードがないので、初期化するフィールドが複数あって不特定の場合はbuilder patternを使って初期化するのが一般的みたい。

builderパターン
struct Hoge {
    x: i32,
    y: i32,
}

struct HogeBuilder {
    x: i32,
    y: i32,
}

impl HogeBuilder {
    fn new() -> HogeBuilder {
        HogeBuilder{x: 0, y:0}
    }
    fn x(&mut self, x:i32) -> &mut HogeBuilder {
        self.x = x;
        self
    }
    fn y(&mut self, y:i32) -> &mut HogeBuilder {
        self.y = y;
        self
    }
    fn finalize(&self) -> Hoge {
        Hoge {x: self.x, y:self.y}
    }
}

fn main() {
    let hoge = HogeBuilder::new().x(2).y(5).finalize();
    println!("{}", hoge.x);
}

中々カッコイイですな。
でもデフォルト値をもたせることもできるみたいなので自分ならこっち使いそうw

tuple struct

tuple structというフィールドが無名のstructがある。

tuple_struct
struct Human(i32, i32, i32);

でも基本的には通常のstructを使うのが推奨らしい。

普通のstructで書いた場合
struct Human {
    age: i32,
    height: i32,
    weight: i32,
}

唯一有効な利用方法としては、1つしか要素がない場合。
名前付きの新しい型みたいに定義できるため。
詳しくはここ

Enum

Enumは以下のように定義する。

enum
enum Grade {
    A(i32),
    B(i32),
    C(i32),
    P(i32),
    NP(i32),
}

Enumにメソッドを生やすこともできる。

enumにメソッドを生やす
impl Grade {
    fn is_a(&self) -> bool {
        match self {
            &Grade::A(_) => true,
            _ => false
        }
    }
}

使うときはこんな感じ

enumを使う
let a = Grade::A(90);
println!("{}", a.is_a());

例が良くなくてすいません。。。

とりあえずEnumは後述のmatchと相性が良さそう。

match

ということで、matchというswitch文みたいなのがある。

match
let x = 3;

match x {
    1 => println!("one"),
    2 => println!("two"),
    _ => println!("something else"),
}

最後の_はデフォルトを表すもので今回の場合は無いとコンパイルエラーになる。
matchもexpressionなので、結果を値に代入することもできる。

matchを使った代入
let x = 3;
let x_string = match x {
    1 => "one",
    2 => "two",
    _ => "something else",
};
println!("{}",x_string);

matchにはpatternが色々あって、例えば複数のときは

複数のmatch
1 | 2 => println!("one or two"),

範囲の時は

範囲のmatch
1 ... 5 => println!("one through five"),

また、マッチしたパターンの値をバインドすることもできて、

matchしたパターンの値をバインド
e @ 1 ... 5 => println!("got a range element {}", e),

パターンはその他色々あるので公式参照。

matchは網羅的である必要があるので、先にも書いた通り、int型とかでmatchする場合はデフォルト_が必須になる。
これはenumのmatchをするときが良い感じで、例えば以下のenumをmatchするときはenumの全要素を網羅できているのでデフォルトは不要

enumのmatch
enum Hoge {
    Foo(String),
    Bar(String),
}

fn match_hoge(h: Hoge) {
    match h {
        Hoge::Foo(_) => println!("hoge"),
        Hoge::Bar(_) => println!("bar"),
   }
}

なんだけど、この時にHogeにFooとBar以外の別の項目を追加した場合はmatchのところがコンパイルエラーになるのが安全ぽくて良さげ。

trait

traitもある。こんな感じ。

trait
trait HasArea {
    fn area(&self) -> f64;
}

struct Triangle {
    height: f64,
    base: f64,
}

impl HasArea for Triangle {
    fn area(&self) -> f64 {
        self.height * self.base / 2.0
    }
}

struct Square {
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let t = Triangle {height:5.0, base:3.0};
    let s = Square {side:2.0};
    print_area(t);
    print_area(s);
}

traitは以下のようにデフォルトメソッドを持つことができる。

デフォルトメソッド
trait Foo {
    fn bar(&self);

    fn baz(&self) { println!("We called baz.");}
}

barだけimplすればOK。

traitは既存の型(i32とか)に追加することもできるが非推奨。
上記のジェネリクスに他のtraitを追加する場合は、<T: HasArea + HasPerimeter>みたいに書く。
複数のジェネリクスを使う場合は以下のいずれかの書き方ができる。

  • fn foo<T: Clone, K: Clone + Debug>(x: T, y: K)
  • fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug

where付けれるのが斬新なんだろうけどあんまり見やすい気はしないかもw
数が増えたら効果あるかも?

Generics

Genericsもある。他の言語と大差ないし、traitのところで書いちゃったので省略。

static dispatchとdynamic dispatch

  • static dispatch
    例えば以下があったとする。

    static_dispatch
     trait Foo {
     		fn bar(&self);
     }
    
     impl Foo for u8 {
     		fn bar(&self){
     				println!("u8: {}", *self);
     		}
     }
    
     impl Foo for String {
     		fn bar(&self){
     				println!("string: {}", *self);
     		}
     }
    
     fn run_bar<T: Foo>(x: T) {
     		x.bar();
     }
    
     fn main() {
     		let x = 1u8;
     		let y = "Hello World!".to_string();
    
     		run_bar(x);
     		run_bar(y);
     }
    

    これは実際は以下のようなコードが作られてそれぞれが呼ばれるようなイメージらしい。

    実際のイメージ
     fn run_bar_u8(x: u8) {
     		x.bar();
     }
    
     fn run_bar_string(x: String) {
     		x.bar();
     }
    

    コンパイル時にどのメソッドを呼ぶか静的に分かるため、inline化することができ一般的には高速になる。
    だがバイナリサイズが大きくなってしまう。
    そのため、dynamic dispatchを使うこともできる。
    ただし、基本的にはstatic dispatchが推奨。

  • dynamic dispatch
    castを使って以下のようにする。

    castを使ったdynamic_dispatch
     fn run_bar(x: &Foo) {
     		x.bar();
     }
     fn main() {
     		let x = 1u8;
     		run_bar(&x as &Foo);
     }
    

    もしくはcoerceを使って以下のようにする。

    coerceを使ったdynamic_dispatch
     fn run_bar(x: &Foo) {
     		x.bar();
     }
    
     fn main() {
     		let x = "Hello World!".to_string();
     		run_bar(&x);
     }
    

    これで実行は遅くなるが、サイズの肥大化は防ぐことができる。

ownership, borrowing, lifetime

rustで特徴的なものの一つ。
rustではmutableな値Aに対してAを参照するBがある場合、値Aは参照Bがスコープ外になるまで変更できない。

エラーになる。
let mut a = 3;
let b = &mut a;

*b = 4;

// これはエラーになる。
// a = 5;

回避方法は、コピーするか、参照Bのスコープを限定する。

コピーして回避する
let mut a = 3;
let b = &mut a.clone();

*b = 4;
a = 5;
スコープを限定して回避する
let mut a = 3;
{
    let b = &mut a;
    *b = 4;
}
a = 5;

この辺の仕組みはownership等の概念を理解すれば分かってくる。

ownership

参照に対するownershipというものがある。
たとえば以下のコードはコンパイルエラーになる。
(いきなりBoxが出てきたが、Boxはスタックじゃなくてヒープに値を格納するためのものみたい)

エラーになる
fn main() {
    let x = Box::new(5);
    add_one(x);
    println!("{}", x);
}

fn add_one(mut num: Box<i32>) {
    *num += 1;
}

これはadd_oneのnumにhandle(と呼ぶらしい。ポインタみたいなもの)が渡された時点でxのownershipはnumに移りxはdeallocateされるため。
これを(後述のborrowingを使わずに)やりたければ、以下のように書く必要がある。

エラーにならない
fn main() {
    let x = Box::new(5);
    let y = add_one(x);
    println!("{}", y);
}

fn add_one(mut num: Box<i32>) -> Box<i32> {
    *num += 1;
    num
}

borrowing

ownershipを渡すんじゃなくてborrowingを使って一時的に貸すこともできる。
こんな感じで書く。

borrowingを使う
fn main() {
    let mut x = 5;
    add_one(&mut x);
    println!("{}", x);
}

fn add_one(num: &mut i32) {
    *num += 1;
}

引数の型に&を付けるとborrowingになる。
ownershipの例と違って、関数内で変えた値が呼び出し元に反映されている。
mutつかない場合の例をownershipを渡す場合とborrowingでやる場合で並べると以下のような感じ。

ownershipを渡すのでNGになる
fn main() {
    let x = Box::new(5);
    add_one(x);
    // 以下は既にownershipをadd_oneに渡してしまっているのでNGになる。
    //println!("{}", x);
}

fn add_one(num: Box<i32>) {
    println!("{}",*num);
}
borrowingなのでOKになる
fn main() {
    let x = Box::new(5);
    add_one(&x);
    // borrowingなので以下はOK
    println!("{}", x);
}

fn add_one(num: &Box<i32>) {
    println!("{}",*num);
}

lifetime

lifetimeという概念もある。borrowingしたけど、元がdeallocateして参照がなくなっちゃったみたいな状態を防ぐために使う。
なお上記のadd_oneはlifetimeを省略した書き方をしている。lifetimeを付けて書くと、以下のようになる。

lifetimeを省略せずに書く
fn add_one<'a>(num: &'a mut i32) {
    *num += 1;
}

'aがlifetimeを表す。名前は何でも良い。2つ以上のlifetimeを定義することもできる。

staticという名前を持つlifetimeは特別で、常に有効なlifetimeになる。
例えばグローバルな値を定義するときなどに使う。

staticなlifetime
let x: &'static str = "Hello, world.";

lifetimeはまだイマイチ把握できていないのでドキュメントが整ったら追記するかも。

closure

closureもある

closure
let num = 5;
let add_num = |x: i32| x + num;
println!("{}",add_num(3));

名前あり関数と違って型宣言(上記だと: i32の部分)は省略できる。
もちろん{}で囲って複数行に書くこともできる。

なお、実際はborrowしているので、以下はコンパイルエラーになる。

let mut num = 5;
let add_num = |x: i32| x + num;

let y = &mut num;

add_numがnumをborrowしているのにyが同じスコープでborrowしようとしているから。
回避するなら以下のようにする必要がある。

let mut num = 5;
{
    let add_num = |x: i32| x + num;
}
let y = &mut num;

moveを使うとownershipを取ることもできる。
以下の例はborrowしているので、最後に表示されるのは10。

moveを使わないとborrowになる
let mut num = 5;
{
    let mut add_num = |x: i32| num += x;
    add_num(5);
}
println!("{}",num);

一方で以下はnumnumのコピーのownershipを取るため、最後に表示されるのは5。

moveを使ってownershipを取る
let mut numnum = 5;
{
    let mut add_numnum = move |x: i32| numnum += x;
    add_numnum(5);
}
println!("{}",numnum);

closureはtraitらしいけどあんまり理解できてないので、あとでここをちゃんと読もう。

crateとmodule

rustでは他の言語でlibraryとかpackageと呼ばれているものはcrateと呼ぶ。
crateは中にmoduleを持つ。moduleは中で複数のsub-moduleをツリー構造で持つことができる。
moduleはデフォルトだとすべてprivate。外からアクセスしたい場合はpubを付ける。

例えば、animalというプロジェクトを作って(cargo new animal)以下のような構造のlib.rsを作れば良い。

animal/src/lib.rs
pub mod dog {
    pub mod action {
    }
}

mod cat {
    mod action {
    }
}

複数ファイルに分けることもできる。
以下のようなディレクトリ構造で、

animalプロジェクトのディレクトリ構造
animal/
├── Cargo.toml
└── src
    ├── cat
    │   ├── action.rs
    │   └── mod.rs
    ├── dog
    │   ├── action.rs
    │   └── mod.rs
    └── lib.rs

それぞれの中身は以下の通り

src/lib.rs
pub mod dog;
mod cat;
src/dog/mod.rs
pub mod action;
src/dog/action.rs
pub fn bark() {
    println!("bowwow!");
}

cat側のファイルは省略。
そして、これを呼び出す場合はextern crateを使う

extern crate animal;

fn main() {
    animal::dog::action::bark();

    //以下はpubつけてないのでエラーになる。
    //animal::cat::action::bark();
}

なげーよwってなると思うので、そーゆーときはuseを使う

extern crate animal;
use animal::dog::action;

fn main() {
    action::bark();
}

色々書き方ができて、use animal::dog::action::barkとやればbark()で呼べるし、
use animal::dog::action::{bark, eat};とかuse animal::dog:*;みたいな感じでまとめることもできる。
mainの中にuse書いてもOK。

標準ライブラリ(crate)はextern crateしなくてもuseだけでOK。
標準じゃない外部crateはCargo.tomlに設定書いてcargo buildすれば自動でダウンロードしてきてくれる。
標準じゃないcrateは先に書いたcrates.ioで探せる。

エラーハンドリング

panicとfailureがある。
panicはリカバリできない(プロセスではなくスレッドがクラッシュする)がfailureはできる。

panicはpanic!()マクロを呼べばOK。

failureのエラーハンドリングにはOptionResultを使う。
Optionは値があったらSome(i)を返して、ない場合はNoneを返すenum。例えばStringのfindメソッドはOptionを返す。

Option
let s = "hoge";
s.find("o"); // Some(1)
s.find("a"); // None

一方、ResultはOK(T)とErr(E)を持つenum。Optionだとエラーの原因とかが分からないので、基本的にはResultを使うべき。

Result
fn check_num (x: i32) -> Result<String, String> {
    match x {
        1 => Ok("one".to_string()),
        2 => Ok("two".to_string()),
        _ => Err("out of range!".to_string()),
    }
}

fn main() {
    check_num(2);// Ok("two")
    check_num(3);// Err("out of range!")
}

try!()マクロを使うとエラーハンドリングを書きやすくしてくれる。
上のcheck_num関数を呼び出す処理を関数化するとこんな感じ。

try!マクロの例
fn check(x: i32) -> Result<String, String> {
    try!(check_num(x));
    return Ok("ok!".to_string());
}

tryの中が失敗すると呼び出し元にそのままエラーが返る。

failureをpanicにしたい場合は、上記の例を使うと以下のようにする。

failureをpanicにしてしまう
s.find("a"); // Option::None
s.find("a").unwrap(); // Panic!

check_num(3);// Result::Err("out of range!")
check_num(3).unwrap();// Panic!

2番め(Resultを返す場合)に関しては以下のようにするとpanicにメッセージを渡すことができる。

panicにメッセージを渡す
let rr = check_num(3).ok().expect("should be 1 or 2");

concurrency

スレッドは以下のように生成する。

スレッド生成
use std::thread;

fn main() {
    for i in 1..10 {
        thread::spawn(move || {
            println!("hellow thread!");
        });
    }
    thread::sleep_ms(50);
}

thread::spawnはスレッド生成をしていて中はclosureになっている。

スレッド外で宣言した変数をスレッド内で操作するようなコードは他の言語ではよくあるが、data raceを簡単に引き起こしてしまう。
これはrustで以下のようにやろうとするとコンパイルエラーになる。

エラーになる
use std::thread;

fn main() {
    let v = vec![1,2,3];

    for i in 0..3 {
        thread::spawn(move || {
            println!("hello thread! {}", v[i]);
        });
    }
    thread::sleep_ms(50);
}

これを実行できるようにするにはArcを使う必要がある。
Arcとはatomic reference counterのことで、Sync traitを実装しておりスレッド間でデータを共有するためのもの。
Arcのcloneメソッドを呼ぶと同じ値を指す別の参照を作り、reference counterをカウントアップする。
また、スレッドローカルなreference counterとしてRcというものもある。

Arcを使って実行可能にした例
use std::sync::Arc;
use std::thread;

fn main() {

    let v = vec![1,2,3];
    let arc = Arc::new(v);

    for i in 0..3 {
        let in_v = arc.clone();

        thread::spawn(move || {
            println!("hello thread! {}", in_v[i]);
        });
    }

    thread::sleep_ms(50);
}

スレッド内で値を変更する場合はMutexを使う。

Mutexを使って値を変更する。
use std::sync::{Arc,Mutex};
use std::thread;

fn main() {

    let v = vec![1,2,3];
    let arc = Arc::new(Mutex::new(v));

    for i in 0..3 {
        let in_v = arc.clone();
        thread::spawn(move || {
            in_v.lock().unwrap()[i] += 1;
        });
    }

    thread::sleep_ms(50);
    for i in 0..3 {
        println!("after: {:?}", arc.lock().unwrap()[i]);// 2,3,4が出力される
    }
}

これで基本OKなんだけど、thread::sleepで待ち合わせはダサい。
そんなときはchannelで待ち合わせる。

rust:channelを使って待ち合わせる。
use std::sync::{Arc,Mutex};
use std::thread;
use std::sync::mpsc::channel;

fn main() {

    let v = vec![1,2,3];
    let arc = Arc::new(Mutex::new(v));
    let (tx, rx) = channel();

    for i in 0..3 {
        let (in_v, tx) = (arc.clone(), tx.clone());
        thread::spawn(move || {
            in_v.lock().unwrap()[i] += 1;
            tx.send(in_v).unwrap();// unwrapはsendが失敗したらpanicにする手抜きエラーハンドリング
        });
    }

    for i in 0..3 {
        rx.recv().unwrap();// unwrapはrecvが失敗したらpanicにする手抜きエラーハンドリング
        println!("after: {:?}", arc.lock().unwrap()[i]);
    }
}

macro

今まで出てきたprintln!とかvec!はmacro

例えば、let x: Vec<u32> = vec![1, 2, 3];は以下のようなことをしているような感じ。

vecマクロがやってること
let x: Vec<u32> = {
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
};

実際の定義はこんな感じになっているらしい。

vecマクロの中身(?)
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

macroはもちろん自分で定義することもできるが、コードを分かりにくくする欠点がある。
macroの書き方はここここに詳しく書かれている。
結構学習コストが高そうだ。。。

unsafe

unsafeブロック(unsafe{ }で囲われたブロック))を使うとコンパイラのチェックを部分的に回避できる。

例えば以下は別の型への変換を無理やり行う例。

unsafeの例
fn main() {
    use std::mem;

    let x: &[u8] = &[65,66,67];

    let s: &str = unsafe { mem::transmute(x) };
    println!("{}",s);// ABCが出力される
}

unsafeはraw pointerとかFFI(後述)使うときに使うのが一般的。というかraw pointer自体もFFIでよく使われるものらしい。
raw pointerとは*const Tもしくは*mut Tで書くもの。
ownership等の制約から逃れられるが、ポインタが指している先に実際にデータがあるかは保証されないし、メモリ管理も自分で行わなければならない。

raw_pointerの例
fn main() {
    let raw_p: *const i32 = &1;

    // raw pointeの参照先はデータがあるかが保証されないので、これはコンパイルエラーになる
    // println!("{}", *raw_p);

    // unsafeで囲めばアクセスできる
    unsafe {
        println!("{}", *raw_p);
    }
}

FFIは次の章で。

FFI(Foreign Function Interface)

名前の通り外部の処理(Cの処理とか)を呼ぶ方法。
ここに例があるけど凄く長いのと、snappyっていう使ったことないライブラリ使った例なので一番シンプルに書くとこんな感じ。
ちなみにやり方が分からず&情報が出てこず大分苦戦した。。。

  • rustのソース

    main.rs
     extern crate libc;
    
     #[link(name = "add_one")]
     extern {
     		fn add_one(i: libc::c_int) -> libc::c_int;
     }
    
     fn main() {
     		let i = 10;
     		let result = unsafe { add_one(i) };
     		println!("{} + 1 = {}", i, result);
     }
    
  • Cのソース

    add_one.c
     int add_one(int i) {
     		return i + 1;
     }
    
  • soファイル作成

    soファイル作成
     gcc -fPIC -shared -o libadd_one.so add_one.c
    

    出来上がったlibadd_one.sotarget/debug/deps/に移す。

  • tomlファイルに以下を追記

    Cargo.toml
     [dependencies]
     libc = "0.1"
    

あとはcargo build(run)でOKです。

最後に

とりあえず、前述の通りrust初心者なので、おかしな点があればツッコミ等貰えればと思います。

感想としては、書き方や学習コストはGolangに分があるかと思いますね。
ただGolang使っててRustに移った方の話も見かけるので、もう少しちゃんと勉強したいかなとは思います。
といってもしばらくは様子見っぽいですが。
てか、Golangとは使いドコロが違うんだろうなぁって感じなのに比較されてる記事が多いのが謎でした。

なお、この記事は随時更新するかもです。
てか、あとから見たら最初のstackoverflowのアンケート結果紹介とか邪魔にしか思えないと思うので消すかもw
→本当に邪魔だったので消しましたw

81
82
2

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
81
82

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?