はじめに
stackoverflowが毎年行っている開発者向けアンケートの2015年の結果で好きな言語(ここ1年使ってきて、今後も使い続けたい言語)で3位になったRustについて名前くらいしか知らなかったので入門してみました。
追記
最初はbeta時点の情報でしたが、Stable版リリースに伴って記事更新しました。
といっても動作確認し直したくらいです。
あと、最初に書いてたstackoverflowのアンケート結果紹介はやっぱり邪魔だったので消しましたw
私のどうでもよい コメントは更新履歴から参照してもらえればと思います。
Rustとは
とりあえず箇条書きで。
(なおrustは始めたばかりなので自分の理解がおかしい点もあるかもしれません。おかしな点に気づいた方は マサカリ ご指摘頂けたらと思います。)
- 背景とマイルストーン
- 2009年にGraydon Hoareによって開発されたもので、2010年にMozilla Researchからアナウンス。
- ただしGraydon Hoareは既にプロジェクトのTechnical Leadをやめているようです。
- バージョン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
公式ページでは以下の方法が勧められている。
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)、以下をしないとダメだった。
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib
ここで取り上げられているのでその内解決するはず。
uninstall
もしもアンインストールする場合は以下
sudo /usr/local/lib/rustlib/uninstall.sh
Hello World
適当にディレクトリを作って、以下の中身で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を作成。
[package]
name = "helloworld"
version = "0.0.1"
authors = [ "hogehoge <hogehoge@example.com>" ]
現在はこんな感じ。
.
├── Cargo.toml
└─── src
└── main.rs
そしてcargo build
を実行するとこんな感じのメッセージが表示されてビルドが完了する。
$ cargo build
Compiling hello_world v0.0.1 (file:///home/hogehoge/work/rust/projects/helloworld)
ビルドすると、同じディレクトリにCargo.lockというファイルが作られる。
中には依存パッケージの情報等が書かれるようだが、今回は無いので以下のようにシンプルなものになる。
なお、このファイルは自分では触る必要は無さそう。
[root]
name = "helloworld"
version = "0.0.1"
実行はcargo run
で行う。
$ 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 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つ目の書き方が推奨
fn foo(x: i32) -> i32 {
if x < 0 { return x; }
x + 1
}
fn foo(x: i32) -> i32 {
if x < 0 { return x; }
return x + 1;
}
fn foo(x: i32) -> i32 {
if x < 0 {
x
} else {
x + 1
}
}
戻り値が無い関数はdiverging functionと呼ぶ。
戻り値を書く場所に!(divergesと読む)を書く。
fn diverges() -> ! {
panic!("This function never returns!");
}
変数に代入する式も書ける。(どんな型の変数でもOK)
let i = diverges();
(戻り値無しの関数と同じだと思ってたんですが、コメントでご指摘頂いたので以下修正しました。)
戻り値無しの関数と同じものかと思っていたが、戻り値無しの関数は実際はunit type(()
で表される)を返すものとのこと。
よって、例えば以下の場合、diverging functionだとiが0以上の場合には()
を返すことになってしまいエラーになるが、
fn mayreturn(i: i32) -> ! {
if i < 0 {
panic!("The argument is negative!");
}
}
以下の戻り値無し関数の場合は正常に実行できる。
fn mayreturn(i: i32) {
if i < 0 {
panic!("The argument is negative!");
}
}
実際に後者を呼び出して戻り値を見てみると()
が出力される。
let res = mayreturn(3);
println!("{:?}",res);// ()が出力される
プリミティブ型
-
boolean
booleanlet y: bool = false;
-
char
4バイトのUnicode Scalar Value。charlet x = 'x'; let fax = '℻'; let hearts = '💕'; let turn_a_gundam = '∀';
-
number
型宣言しない場合は整数だとi32、浮動小数点数の場合はf64になる。unsignedはu32みたいに書く。i/uは8,16,32,64がある。fは32,64。
マシンのポインタのサイズによって変わるisize
とusize
というものもある。 -
配列
配列let a = [1, 2, 3]; let b = [0; 10]; // 全部0で初期化される。mut付けないと意味なさそう // サイズ取得 b.len(); // インデックスアクセス b[0];
-
slice
sliceは他のデータ構造への参照。slicelet 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は以下のように宣言するTuplelet 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 x == 5 {
println!("x is five!");
} else if x == 6 {
println!("x is six!");
} else {
println!("x is not five or six :(");
}
だけど、ifはexpressionなのでこんなこともできる。
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文みたいなもの。詳細は後述。iteratorlet mut range = 0..10; loop { match range.next() { Some(x) => { println!("{}", x); }, None => { break } } }
以下も内部的にiteratorを回している例。
iteratorその2let a = [1,2,3]; for i in &a { println!("{}", i); }
こんな書き方もできる。
iteratorその3for i in a.iter() { println!("{}", i); }
iteratorに対するconsumerに当たる操作がある。
collectの例let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
その他iteratorに関しては詳しくはこちらを参照。
struct
他の言語と同じように使えるので基本形は省略。
メソッド生やすときはこんな感じでimplキーワードを使う。
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はこんな感じ。
impl Hoge {
fn new(x: i32) -> Hoge {
Hoge{x: x}
}
}
// 呼び出し側
fn main() {
let hoge2 = Hoge::new(3);
}
builder pattern
rustにはメソッドのオーバーロードがないので、初期化するフィールドが複数あって不特定の場合はbuilder patternを使って初期化するのが一般的みたい。
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がある。
struct Human(i32, i32, i32);
でも基本的には通常のstructを使うのが推奨らしい。
struct Human {
age: i32,
height: i32,
weight: i32,
}
唯一有効な利用方法としては、1つしか要素がない場合。
名前付きの新しい型みたいに定義できるため。
詳しくはここ
Enum
Enumは以下のように定義する。
enum Grade {
A(i32),
B(i32),
C(i32),
P(i32),
NP(i32),
}
Enumにメソッドを生やすこともできる。
impl Grade {
fn is_a(&self) -> bool {
match self {
&Grade::A(_) => true,
_ => false
}
}
}
使うときはこんな感じ
let a = Grade::A(90);
println!("{}", a.is_a());
例が良くなくてすいません。。。
とりあえずEnumは後述のmatchと相性が良さそう。
match
ということで、match
というswitch文みたいなのがある。
let x = 3;
match x {
1 => println!("one"),
2 => println!("two"),
_ => println!("something else"),
}
最後の_
はデフォルトを表すもので今回の場合は無いとコンパイルエラーになる。
matchもexpressionなので、結果を値に代入することもできる。
let x = 3;
let x_string = match x {
1 => "one",
2 => "two",
_ => "something else",
};
println!("{}",x_string);
matchにはpatternが色々あって、例えば複数のときは
1 | 2 => println!("one or two"),
範囲の時は
1 ... 5 => println!("one through five"),
また、マッチしたパターンの値をバインドすることもできて、
e @ 1 ... 5 => println!("got a range element {}", e),
パターンはその他色々あるので公式参照。
matchは網羅的である必要があるので、先にも書いた通り、int型とかでmatchする場合はデフォルト_
が必須になる。
これはenumのmatchをするときが良い感じで、例えば以下のenumをmatchするときはenumの全要素を網羅できているのでデフォルトは不要
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 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_dispatchtrait 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_dispatchfn run_bar(x: &Foo) { x.bar(); } fn main() { let x = 1u8; run_bar(&x as &Foo); }
もしくはcoerceを使って以下のようにする。
coerceを使ったdynamic_dispatchfn 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を使って一時的に貸すこともできる。
こんな感じで書く。
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でやる場合で並べると以下のような感じ。
fn main() {
let x = Box::new(5);
add_one(x);
// 以下は既にownershipをadd_oneに渡してしまっているのでNGになる。
//println!("{}", x);
}
fn add_one(num: Box<i32>) {
println!("{}",*num);
}
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を付けて書くと、以下のようになる。
fn add_one<'a>(num: &'a mut i32) {
*num += 1;
}
'a
がlifetimeを表す。名前は何でも良い。2つ以上のlifetimeを定義することもできる。
staticという名前を持つlifetimeは特別で、常に有効なlifetimeになる。
例えばグローバルな値を定義するときなどに使う。
let x: &'static str = "Hello, world.";
lifetimeはまだイマイチ把握できていないのでドキュメントが整ったら追記するかも。
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。
let mut num = 5;
{
let mut add_num = |x: i32| num += x;
add_num(5);
}
println!("{}",num);
一方で以下はnumnumのコピーのownershipを取るため、最後に表示されるのは5。
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を作れば良い。
pub mod dog {
pub mod action {
}
}
mod cat {
mod action {
}
}
複数ファイルに分けることもできる。
以下のようなディレクトリ構造で、
animal/
├── Cargo.toml
└── src
├── cat
│ ├── action.rs
│ └── mod.rs
├── dog
│ ├── action.rs
│ └── mod.rs
└── lib.rs
それぞれの中身は以下の通り
pub mod dog;
mod cat;
pub mod action;
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のエラーハンドリングにはOptionかResultを使う。
Optionは値があったらSome(i)を返して、ない場合はNoneを返すenum。例えばStringのfindメソッドはOptionを返す。
let s = "hoge";
s.find("o"); // Some(1)
s.find("a"); // None
一方、ResultはOK(T)とErr(E)を持つenum。Optionだとエラーの原因とかが分からないので、基本的には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関数を呼び出す処理を関数化するとこんな感じ。
fn check(x: i32) -> Result<String, String> {
try!(check_num(x));
return Ok("ok!".to_string());
}
tryの中が失敗すると呼び出し元にそのままエラーが返る。
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にメッセージを渡すことができる。
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というものもある。
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を使う。
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で待ち合わせる。
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];
は以下のようなことをしているような感じ。
let x: Vec<u32> = {
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
};
実際の定義はこんな感じになっているらしい。
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
macroはもちろん自分で定義することもできるが、コードを分かりにくくする欠点がある。
macroの書き方はこことここに詳しく書かれている。
結構学習コストが高そうだ。。。
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等の制約から逃れられるが、ポインタが指している先に実際にデータがあるかは保証されないし、メモリ管理も自分で行わなければならない。
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.rsextern 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.cint add_one(int i) { return i + 1; }
-
soファイル作成
soファイル作成gcc -fPIC -shared -o libadd_one.so add_one.c
出来上がった
libadd_one.so
をtarget/debug/deps/
に移す。 -
tomlファイルに以下を追記
Cargo.toml[dependencies] libc = "0.1"
あとはcargo build(run)でOKです。
最後に
とりあえず、前述の通りrust初心者なので、おかしな点があればツッコミ等貰えればと思います。
感想としては、書き方や学習コストはGolangに分があるかと思いますね。
ただGolang使っててRustに移った方の話も見かけるので、もう少しちゃんと勉強したいかなとは思います。
といってもしばらくは様子見っぽいですが。
てか、Golangとは使いドコロが違うんだろうなぁって感じなのに比較されてる記事が多いのが謎でした。
なお、この記事は随時更新するかもです。
てか、あとから見たら最初のstackoverflowのアンケート結果紹介とか邪魔にしか思えないと思うので消すかもw
→本当に邪魔だったので消しましたw