Rust 入門 #2 : 所有権システムと可変性
「Rust 入門してる #1」 に続き、ぼちぼちと Rust を学んでます。 今日は所有権システムと可変性についてのメモです。 4 月 14 日に Rust 1.8 がリリースされたので、バージョン 1.8 のドキュメントを読んでます。 *1
所有権システムについては下記の 3 ページを読みました。
可変性については次のページ。
基本的にはそんなに難しいことは書かれてないですが、後で説明される内容が出てきたり説明があまりうまくなかったりで理解が難しい感じがしますね。 まあざっくりと理解して読み進めていけば良さそうです。
所有権システム (Ownership system)
- Rust の最も独特で感動的な機能であり、Rust 開発者はこれに精通すべきとのこと。
- メモリ安全がこの機能の大きな目的である。
- コンセプトは次の 3 つ。
- 所有権 (ownership)
- 借用 (borrowing) とそれに関連する機能である 「参照 (reference)」
- 生存期間 (lifetimes)
Meta
所有権 (Ownership)
- 変数束縛 (variable binding) には、それらの結合先の 「所有権を持っている」 かどうかを表すプロパティがある。
- 束縛がスコープを抜けると、Rust は束縛されているリソースを解放するであろう。
fn foo() { let v = vec![1, 2, 3]; }
上の例だと、新しい vector がスタック上に作られ、各要素のためのスペースはヒープ上に生成される。 v
がスコープから外れると、Rust は vector に関するリソースを解放する (ヒープに割り当てられたメモリも)。
Move semantics
- 任意のリソースに対して所有権を持つ束縛は、プログラム中に 1 つしか存在できない?
- あるリソースに対して所有権を持つ束縛を別の束縛に割り当てた場合、所有権は新しい束縛に移り、古い束縛経由でリソースにアクセスしようとしてもコンパイルエラーが発生する。
fn foo() { let v = vec![1, 2, 3]; let v2 = v; // 所有権が v から v2 に移る。 // v 経由で vector の操作をしようとしてもコンパイルエラーになる。 }
上は別の変数束縛への割り当ての例だが、関数の引数として渡す場合もやはり所有権は移る。
Copy
型
- 上で見たように、ある束縛の値を別の束縛に割り当てると普通は所有権が移る。 そのため、古い束縛を使おうとするとコンパイルエラーになる。
- 所有権が移らないような型を定義する場合は、
Copy
トレイトを実装することになるらしい。- プリミティブ型は
Copy
トレイトを実装している。
- プリミティブ型は
参照と借用 (References and Borrowing)
単純に引数で所有権を受け取る関数を書いた場合、呼び出し元に所有権を返すために戻り値で所有権を返す必要がある。 それはめっちゃ面倒なので、参照を受け取るようにして回避する。
参照と借用
参照はリソースを所有するのではなく、所有権を借りる (借用)。 スコープを抜けたときにはリソースを解放するのではなく所有権を返す。
生存期間 (Lifetimes)
- 生存期間とは、参照が有効であるスコープを記述するもの。
- 全ての参照は生存期間を持つが、一般的には明示しなくても良い。 (省略可能。)
- 特別な生存期間として 「static」 がある。 これはプログラム全体を表す生存期間である。
- 生存期間を指定する場合、参照を表す型の
&
の後ろに'lifetime
を付ける。- 例えば
fn calc(val: &'static i32) { ... }
という感じ。
- 例えば
- ジェネリックパラメータとして生存期間を宣言することもできる。
- 例えば
fn calc<'a>(val: &'a i32) { ... }
という感じ。
- 例えば
生存期間の省略 (lifetime elision)
- Rust ではシグネチャでの型推論は基本的に禁止されているが、関数のシグネチャにおいては 「生存期間の省略」 が適用される。
- 関数の引数に関連する生存期間を入力生存期間 (input lifetime)、関数の戻り値に関連する生存期間を出力生存期間 (output lifetime) という。
- 「生存期間の省略」 のルールは次のとおり。
- 関数の引数のうち、省略された生存期間は全て異なる生存期間のパラメータを持つ。
- 入力生存期間が 1 つのみの場合 (省略されているかどうかに関わらない)、返り値に関連する生存期間のうち省略されているものは全て、入力生存期間と同じ生存期間となる。
- 入力生存期間が複数ある場合でも、引数の 1 つが
&self
か&mut self
の場合は、返り値に関連する生存期間のうち省略されているものは全て、self
の生存期間と同じ生存期間となる。 (これはまだよくわかってない。) - 上記以外は全てエラーとなる。
可変性 (Mutability)
- Rust で 「イミュータブル」 というのは、必ずしも 「変更できない」 ことを意味するわけではない。
- 通常の参照と mut 参照が Rust における不可変性の基礎となっている。
- すなわち、「イミュータブル」 であれば、複数の参照が存在しても安全であるということである。
*1:前回は 1.7 のドキュメントを読んでました。 基本的には変わりないはずです。
Rust 入門してる #1
最近 Rust を学び始めました。 まだちまちま構文を追っているところですが、ドキュメントがしっかりしていて学びやすい印象です。 (手元では、現在の最新の安定板である Rust 1.7 を使っています。)
Web 上で実行できる環境も提供されています。
進捗 (4.1 節から 4.6 節まで)
とりあえず変数結合 (variable bindings) から初めて、関数やプリミティブ型、コメント、if
やループといった制御構文あたりをざっと見てみました。
まだここら辺はそんなに特徴的な部分はないですね。 char
型の保持する値が Unicode スカラ値である、とか、例外により処理が戻らない関数 (diverging functions) のための戻り値の型は !
で宣言、といったあたりが個人的にはちょっと新鮮でした。
次の節は 「Ownership」 だったり、その次は 「References and Borrowing」 だったりするので、そのあたりから Rust っぽさが出てきそうです。
サンプルコード
各節の内容をコードに落とし込んだものです。
fn main() { hello_variable_bindings(); hello_functions(); hello_primitive_types(); hello_comments(); hello_if(); hello_loops(); } // 4.1 Variable bindings // See : https://doc.rust-lang.org/book/variable-bindings.html fn hello_variable_bindings() { println!("=== 4.1 Variable bindings ==="); // `let` で変数結合 (variable binding) の割り当てを行う。 let a = 100; println!("`a` is {}", a); // `let` 式の左辺にはパターンも使える。 let (b, c) = (1, 2); println!("`b` is {}, c is {}", b, c); // コロンの後に型を書いて型注釈 (type annotation) も可能。 let d: i32 = 5; println!("`d` is {}", d); // 通常は結合は不変 (immutable) だが、`mut` で可変 (mutable) にできる。 let mut e = 100; println!("`e` is {}", e); e = 200; println!("`e` is {}", e); // 結合の初期化は使用までに行われればよい。 (使用までに行われなければならない。) let f; f = 10; println!("`f` is {}", f); // スコープとシャドーイング。 { // スコープはブロック内。 let g = 20; println!("`g` is {}", g); // ブロックの外の結合を使用できる。 println!("outer `f` is {}", f); // 同じ名前の結合で前の結合を覆い隠せる (shadow)。 (外のブロックの変数結合を隠す例。) let f = 30; println!("inner `f` is {}", f); } // ここからはもう上のブロック内の `z` は見えない。 // 同じスコープの結合も隠せる (shadow)。 let f = "Another type"; // Mutable な変数結合に割り当てし直す場合と異なり、別の型の値を割り当てられる。 println!("`f` is {}", f); } // 4.2 Functions // See : https://doc.rust-lang.org/book/functions.html fn hello_functions() { println!("=== 4.2 Functions ==="); // `fn` の後に関数名、括弧で囲まれた引数、ブレースを続けて関数定義。 // 引数には型の宣言が必要。 fn print_sum(x: i32, y: i32) { println!("sum is {}", x + y); } print_sum(1, 2); // 引数の戻り値の型は `->` に続けて宣言する。 // 最後の式が戻り値になる。 // (返り値の型が `unit` 型 (`()`) の場合は省略できるっぽい?) fn sum(x: i32, y: i32) -> i32 { x + y } print_sum(sum(1, 2), 3); // 最後にセミコロンがあると戻り値は `unit` 型になる。 fn sum2(x: i32, y: i32) -> () { x + y; } sum2(1, 2); // Rust は主には式 (expression) ベースで、2 種類の文 (statement) を除いて他は全て式。 // 1 つめの文、‘declaration statements’ について。 // 例えば `let` による変数結合は文。 よって `let x = (let y = 100)` みたいな記述はできない。 // 結合済みの変数への割り当ては式だが、その値は空のタプルになる。 let mut v1 = 100; println!("v1 is {}", v1); let v2 = v1 = 200; // `v2` は `200` ではなく `()`。 println!("v1 is {}, v2 is {:?}", v1, v2); // 2 つめの文、‘expression statements’ は、任意の式を文にするもの。 // Rust では式と式をセミコロンで分割し、文の連なりとして記述していく。 // (なので、上で見たように関数の最後にセミコロンがあるかどうかで返り値が変化する。) // `return` キーワード。 fn foo(x: i32) -> i32 { if x < 0 { return x } x + 1 } println!("`foo(-5)` is {}", foo(-5)); println!("`foo(5)` is {}", foo(5)); // 処理が戻らない diverging functions のための戻り値は `!` で宣言。 fn do_panic() -> ! { // `panic!` マクロは現在のスレッドの実行をクラッシュさせる。 panic!("Always panic"); } if false { // 実際にはクラッシュしないように。 // Diverging function の返り値は任意の型として扱える。 let panic_return: i32 = do_panic(); } // 関数を指す変数結合も生成できる。 let sum_func: fn (i32, i32) -> i32 = sum; println!("`sum_func(5, 6)` is {}", sum_func(5, 6)); } // 4.3 Primitive Types // See : https://doc.rust-lang.org/book/primitive-types.html fn hello_primitive_types() { println!("=== 4.3 Primitive Types ==="); // `bool` 型 let b1 = true; let b2: bool = false; println!("`b1` is {}, `b2` is {}", b1, b2); // `char` 型 // 単一のユニコードスカラ値 (コードポイントから上位と下位のサロゲートコードポイントを除いたもの) を表す。 4 バイトらしい。 let c1 = 'a'; let c2: char = '💛'; println!("`c1` is {}, `c2` is {}", c1, c2); // 各種数値型 // 符号の有無 (signed/unsigned)、サイズ (fixed/variable)、 // 整数か浮動小数点数 (integer/floating-point) でカテゴリ分けされる。 // 整数は符号付きと符号無しがある。 // 符号付整数の型には `i` が、符号無整数には `u` が使用される。 // Fixed size types は型にそれぞれのビット数 (8、16、32、64) が使用される。 let num1: i32 = -100; // 32 ビット符号付整数 let num2: u8 = 3; // 8 ビット符号無整数 println!("`num1` is {}, `num2` is {}", num1, num2); // Variable sized types は、マシンのポインタサイズに依存するサイズ。 // 符号付は `isize`、符号無は `usize`。 let num3: usize = 8; println!("`num3` is {}", num3); // 浮動小数点数は `f32` と `f64`。 // IEEE-754 single and double precision numbers に相当。 let num4: f32 = 10.5; println!("`num4` is {}", num4); // 配列 // 同一型の要素からなりサイズ固定。 `[T; N]` で型が表現される。 // `T` は要素の型。 `N` はサイズ。 let array1: [i32; 7] = [0, 1, 2, 3, 4, 5, 6]; println!("`array1` is {:?}", array1); // `[elem; size]` という省略表記で、全ての要素を `elem` で初期化したサイズ `size` の配列を生成。 let array2: [i32; 5] = [10; 5]; println!("`array2` is {:?}", array2); // サイズは `len()` で取得可能、各要素は `[n]` で取得可能 (インデックスは 0 から開始)。 println!("Size of `array1` is {}, `array1[0]` is {}", array1.len(), array1[0]); // Slice // メモリ上で隣接する指定の型の値の並びに対するビュー。 // 以下は配列に対する例だが、配列以外の様々なものに対して `&` と `[]` の組み合わせで slice を生成できる。 let complete_slice = &array1[..]; // `array1` のすべての要素を含む slice let middle_slice = &array1[1..4]; // `array1` のインデックス 1 から 3 までの要素を含む slice println!("`complete_slice` is {:?}, `middle_slice` is {:?}", complete_slice, middle_slice); // `str` // これ自身は便利ではないが、`&str` という感じでリファレンスで使用すると便利である。 let str1: &'static str = "Hello, world!"; println!("`str1` is {}", str1); // タプル // 固定サイズの順序付きリスト。 let tuple1: (i32, &str) = (1, "hello"); println!("`tuple1` is {:?}", tuple1); // destructuring let によりタプル内のフィールドにアクセスできる。 let (tuple1_elem1, tuple1_elem2) = tuple1; println!("`tuple1_elem1` is {}, `tuple1_elem2` is {}", tuple1_elem1, tuple1_elem2); // Indexing syntax により、各要素にアクセスすることもできる。 println!("`tuple1.0` is {}, `tuple1.1` is {}", tuple1.0, tuple1.1); // 1 要素のタプルを括弧で囲まれた式と区別するために、カンマを使う。 let tuple2 = (2,); let not_tuple = (2); println!("`tuple2` is {:?}, `not_tuple` is {}", tuple2, not_tuple); // 上で見たように関数も型を持つ。 fn function1(x: i32) -> i32 { x } let function2: fn(i32) -> i32 = function1; println!("`function2` is {:?}", function2); } // 4.4 Comments // See : https://doc.rust-lang.org/book/comments.html fn hello_comments() { println!("=== 4.4 Comments ==="); // 行コメントと doc コメントがある。 // `//` で始まるコメントは行コメント。 // doc コメントは `///` で始める。 /// doc コメントのサンプル用の関数。 /// /// # 例 /// 以下のように使用する。 /// ``` /// do_test(); // 呼び出し /// ``` fn do_test() { println!("hello"); } do_test(); // `///` による doc コメントは、そのコメントの後ろにあるものに対するコメント。 // 別の形式の doc コメントとして、`//!` というものがある。 // これは、そのコメントを含んでいるものに対するコメント。 // モジュールや crate (crate とは?) の内部で使用されるらしい。 // doc コメントについて、さらに詳しいことは次のページに書かれている。 // https://doc.rust-lang.org/book/documentation.html } // 4.5 if // See : https://doc.rust-lang.org/book/if.html fn hello_if() { println!("=== 4.5 if ==="); // 条件が括弧で囲まれないのと本体がブロック必須なこと以外はわりと C に近いっぽい。 let x = 5; if x == 5 { println!("`x` is five!"); } else if x == 6 { println!("`x` is six!"); } else { println!("`x` is not five or six :("); } // 式なので値を返す。 // (`else` がない場合は常に `()` になるらしい。) let y = if true { 10 } else { 20 }; println!("`y` is {}", y); } // 4.6 Loops // See : https://doc.rust-lang.org/book/loops.html fn hello_loops() { println!("=== 4.6 Loops ==="); // 現在のところ `loop`、`while`、`for` の 3 種類。 // 明示的に抜けない限り無限にループする。 loop { println!("In `loop`"); break; } // 条件付き。 let mut count = 0; while count < 4 { println!("In `while, with `count` {}", count); count += 1; } // `for` for x in 0..4 { println!("In `for`, with `x` {}", x); } // `.enumerate()` で index を取得できる。 for (index, value) in (5..8).enumerate() { println!("`index` is {}, `value` is {}", index, value); } // ループの制御には `break` と `continue` が使用できる。 let mut y = 0; loop { y += 1; if y % 2 == 0 { continue } if y > 4 { break } println!("`y` is {}", y); } // ループにラベルを付けて、`continue` や `break` で指定できる。 'outer: for x in 0..5 { 'inner: for y in 0..5 { if x % 2 == 0 { continue 'outer } if y % 2 == 0 { continue 'inner } println!("x: {}, y: {}", x, y); } } }
Gradle のマルチプロジェクト構成におけるプロジェクトの評価順 (設定フェーズにおけるプロジェクト間の依存関係)
Gradle の公式のドキュメントに書いてあるけどどこに書かれてるのか探すのにいつも手間取るのでメモっておく。
前提知識
- Gradle のビルドには、「初期化」 (Initialization) と 「設定」 (Configuration) と 「実行」 (Execution) の 3 つのフェーズがある。 (Chapter 20. The Build Lifecycle)
- マルチプロジェクトの場合、各プロジェクトが評価されていって 「設定」 フェーズが終わり、そのあと 「実行」 フェーズに移る。
- 「実行」 フェーズではタスクの実行が行われるので、タスク間の依存関係により実行順が決まる。
プロジェクトの評価順
- Chapter 24. Multi-project Builds に書かれてる。
- 基本としては、各プロジェクトが名前のアルファベット順に評価される。
- 入れ子になったプロジェクトの場合、上位のプロジェクトから下位のプロジェクトの順でプロジェクトが評価される。
- あるプロジェクト (A とする) の評価時に、別のプロジェクト (B とする) で定義される何かを参照したり変更を加えたりする場合、A より先に B が評価されている必要がある。
- プロジェクトの評価順序に依存関係を持たせるには、
Project#evaluationDependsOn(String)
メソッドが使用できる。 - あるプロジェクトの評価後に処理を実行したい場合は、
Project#afterEvaluate(Cloure)
メソッドなどを使用すると良い。
gif-writer (GifWriter.js) version 0.9.3 をリリースしました
2013 年にリリースした GIF Encoder のライブラリ “GifWriter.js” を npm モジュールにして “gif-writer” としてリリースしました。 バージョンは 0.9.3 です。 *1
変更内容としては以下のとおりです。
- 各ファイルをモジュールに変更。
- パッケージに TypeScript の型定義ファイル (
.d.ts
ファイル) を含めるように。
npm モジュールに型定義を含める話
TypeScript 1.6 からモジュール名解決を npm モジュールから行う機能が入りました。
- Typings for npm packages · Microsoft/TypeScript Wiki · GitHub
- TypeScript 1.6時代の.d.ts管理について意見を述べておく - Qiita
今回のリリースでは、npm モジュールに TypeScript の型定義ファイルを含めています。 よって、TypeScript で書いているプロジェクトで “gif-writer” モジュールを使う場合、単に以下のように import
するだけで型定義が有効になります。
import * as gw from "gif-writer";
これは便利!!
*1:バージョン 0.9.0 と 0.9.1 はパッケージングに失敗したもので、0.9.2 と 0.9.3 の差分は README のみです。
「DroidKaigi 2016」 と 「関モバ #11」 で発表しました
「DroidKaigi 2016」 にて 『5 年続く 「はてなブックマーク」 アプリを継続開発する技術』 というタイトルで、また 「関モバ #11」 で 『ComponentsRecyclerAdapter — RecyclerView で複数の view type や複雑なデータ構造を扱う』 というタイトルで、それぞれ発表しました。
DroidKaigi 2016
5 年前に最初のリリースが行われたアプリを継続的に開発しており、そのための取り組みを共有しました。
要点をまとめると以下のようになります。
- テストは Testing Support Library に頼っている。
- CI については、社内の Jenkins サーバー上でテストの実行やリリースパッケージ作成の自動化を行っている。
- 各種タスクを Gradle タスクとして定義し、タスクの実行をシェルスクリプトで記述。
- 最近は Jenkins Workflow plugin への移行を進めている。 (Groovy での DSL が書きやすいとか、リリースパッケージのアップロード処理の前に人の確認を挟みやすいなどの利点のため。)
- Preview 版 (production flavor の
preview
) を定義して、開発中の機能を preview 版で有効に。- 長い期間の開発でも他ブランチとのコンフリクトをしづらく & チーム内配布で確認しやすく。
- Annotations Support Library を活用。
- 必要に応じてライブラリ的なものを作る。
- 例えば 「関モバ #11」 で発表した
ComponentsRecyclerAdapter
など。
- 例えば 「関モバ #11」 で発表した
Preview 版について補足
開発中の機能を Preview 版 (product flavor の preview
) でのみ有効にする、という話がスライド中に出てきます。 これについて、具体的な方法がわからないということで質問いただきました。 (src/preview/java
下に preview
版専用のソースコードを書くとすると面倒くさすぎるのでは? という。)
具体的には、ソースコードは全て src/main/java
下に置いて、
build.gradle
ファイル内でbuildConfigField
を使って preview 版専用の機能を有効にするかどうかの値を持ったフィールドをBuildConfig
に生成し、- Java のコード中で
BuildConfig
のフィールドを見てif
で条件分岐して機能を有効にしたり無効にしたりする
という感じです。
関西モバイルアプリ研究会 #11
ComponentsRecyclerAdapter を用いて複数の view type や複雑なデータ構造を扱う
複数の item view や複数のデータセットによる複雑なデータ構造を扱いやすくする RecyclerView.Adapter
のサブクラス ComponentsRecyclerAdapter
の紹介です。