Open hysryt opened 4 years ago
プリミティブ型。 メモリのブロックを表す。 ブロック範囲を変更することはできない。
添字を使ってブロック内のデータにアクセスできる。
let ary = [10, 20, 30, 40, 50];
println!("{:?}", ary == ary); // true
println!("{:?}", ary == ary[..]); // true
println!("{:?}", ary == ary[0..]); // true
println!("{:?}", ary == ary[0..5]); // true
println!("{:?}", ary == ary[0..3]); // false
println!("{:?}", ary == ary[0]); // コンパイルエラー
配列は実データ、スライスは参照?
構造体。
struct Man {
name: String,
}
第一引数に self を指定するとメソッドとなる。
impl Man {
fn greet(&self) {
println!("I'm {}.", self.name);
}
}
impl Man {
fn new(name: Into<String>) {
Man { name: name.into() }
}
}
enum Animal {
Dog,
Cat,
}
トレイト。 インターフェイスの定義。
trait Shape {
fn area(&self) -> f64;
}
PHPのトレイトとは全く別の概念なので注意
https://doc.rust-lang.org/std/convert/trait.Into.html
pub trait Into<T> {
fn into(self) -> T;
}
他の型への変換を表すトレイト。 Fromトレイトを実装すると、対となる型に自動的に実装される。
引数の型として使うと便利。
impl Man {
fn new(name: impl Into<String>) -> Self {
Man { name: name.into() }
}
}
https://doc.rust-lang.org/std/convert/trait.From.html
pub trait From<T> {
fn from(T) -> Self;
}
他の型からの変換を表すトレイト。 Intoトレイトと相互関係にある。
基本的にIntoトレイトではなくFromトレイトを実装した方が良い。 Fromトレイトを実装すると自動的にIntoトレイトも実装される。 https://doc.rust-lang.org/src/core/convert/mod.rs.html#557
struct Point<T> {
x: T,
y: T,
}
struct Point_i32 {
x: i32,
y: i32,
}
struct Point<T> {
x: T,
y: T,
}
Point<T>
に関数を定義する場合は以下のようにする。impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
ジェネリクスを使った型に関数を定義する場合、impl
に <T>
が必要。
これはその後につづく Point<T>
の <T>
が具体的な型ではなくジェネリクスであることをコンパイラに説明している。
Point<i32>
のようにジェネリクスの型を特定して関数を定義することも可能。
impl Point<i32> {
fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
}
Point<T>
の T
が特定のトレイトを実装している型のみに関数を定義することも可能。
下記は T
が PartialEq
トレイトを実装している型のみに関数を定義している。impl<T: PartialEq> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
Into
トレイトを実装している。From
トレイトが実装されていない場合、into()
を実行するとコンパイルエラーとなる。
the trait bound `std::string::String: std::convert::From<Test>` is not satisfied
the trait `std::convert::From<Test>` is not implemented for `std::string::String`
note: required because of the requirements on the impl of `std::convert::Into<std::string::String>` for `Test`
トレイト境界 std::string::String: std::convert::From<Test>
が満たされていません。
std::string::String
で std::convert::From<Test>
トレイトが実装されていません。
Test
に対する std::convert::Into<std::string::String>
トレイトの impl による要件です。
以下のようなコードを書いたとする。
```rust
struct Test {}
fn main() {
let t = Test {};
let s: String = t.into();
}
TをTestに置き換えて、
impl<U> Into<U> for Test
where
U: From<Test>,
{
fn into(self) -> U {
U::from(self)
}
}
型推論により、ここで U
は String
だが、String
は From<Test>
トレイトを実装していないため、コンパイルエラーとなる。
Fn
、FnMut
、FnOnce
の少なくともいずれか一つを実装している。クロージャを構造体に含める場合は以下のようにトレイトとジェネリクスを使用する。
struct Test
where T: Fn(i32) -> i32
{
calc: T
}
Fn
を実装したクロージャは何度でも呼び出すことができる。FnMut
と FnOnce
はどちらとも Fn
のスーパーセットであり、Fn
は FnMat
としても FnOnce
としても扱うことができる。FnOnce
は FnMut
のスーパーセットであり、 FnMut
は FnOnce
としても扱うことができる。Fn
系のトレイトは特別であり、Fn(usize, bool) -> usize
というような構文は Fn
系だけの物。FnMut
を格納する変数には mut
が必要let c = || println!("hello!");
c
にはクロージャの参照がバインドされる。
let mut c = || println!("hello!");
c
にはクロージャの可変参照がバインドされる。
let c = move || println!("hello");
c
にはクロージャがムーブされる。
クロージャを返す関数 https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html#returning-closures
クロージャはトレイトによって表現される。 基本的にトレイトを返したい場合は、代わりにトレイトを実装した具象型を返す。 ただし、クロージャは返せる具象型を持たないため返すことができない。
下記のコードはコンパイルできない。
fn returns_closure() -> dyn Fn(i32) -> i32 {
|x| x + 1
}
トレイトオブジェクトを使えば解決。
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
暗黙的なコピーができない型で複製を行う場合、Cloneトレイトを実装し、clone
メソッドを使う。
Rustではいくつかの型は暗黙的なコピーが可能であり、それらの型では代入時や受け渡し時には所有権のムーブは発生せず、元の値を残したまま値がコピーされる。
これらの型はメモリのアロケーションを必要としないため、安全にコピーが可能。
ただしそれら以外の型では、複製を行う場合は Clone トレイトを実装し、clone
メソッドで明示的にコピーを行う必要がある。
String は Clone トレイトを実装している。
let s = String::new();
let copy = s.clone();
#[derive(Clone)]
で Clone の実装が可能
#[derive(Clone)]
struct Morpheus {
blue_pill: f32,
red_pill: i64,
}
pub trait Copy: Clone { }
Copy トレイトは Clone トレイトのサブトレイトである。 そのため Copy トレイトを実装する場合は Clone トレイトも実装する必要がある。
Copy トレイトを実装した場合、代入時などに自動的にビット単位でコピーされる。 この際cloneメソッドは実行されず、単純にビットのコピーが行われる。
構造体に Copy トレイトを実装する場合、そのメンバー全てが Copy を実装している必要がある。
Copyトレイトは暗黙的コピーを可能にする。
let y = x;
とした時、xがCopyトレイトを実装している場合には所有権は移らず、xのコピー(複製)がyに代入される。 このコピーの振る舞いは変更できず、常にビット単位でのコピーとなる。 内部的にcloneを実行する、というようなことはできない。
Cloneトレイトはcloneを持つ単純なトレイトである。 cloneは通常のメソッドと同じであり、型ごとに定義できる。
CopyトレイトはCloneトレイトのサブトレイトであるため、実装する際はCloneトレイトの実装も必要となる。
struct MyStruct;
impl Copy for MyStruct { }
impl Clone for MyStruct {
fn clone(&self) -> MyStruct {
*self
}
}
clone に特別な処理が必要ない場合は上記のように実装する。 selfを返しているが、Copyトレイトを実装しているためコピーセマンティクスによりコピーされる。
CopyトレイトがCloneトレイトのサブトレイトなのはCopyトレイトもCloneトレイトも一括してCloneトレイトとして受け取れるようにするためと思われる。 両方とも x.clone() で複製が可能。
Clone:この型は複製できる。 Copy:この型はビット単位のコピーにより複製できる。
イテレータに関わる諸々をまとめたモジュール。 トレイト、関数、構造体を含む。
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
next()
は、値があるうちは Some<Item>
を返す。
値がなくなったら None
を返す。
値がなくなったあと、さらに next()
を呼び出した時の挙動はイテレータによって異なる。
再び Some<Item>
を返すものもあれば、None
を返すものもある。
next()
以外のメソッドにはデフォルト実装がある。
Iterator
を実装する場合は next()
だけ実装すれば良い。
コレクションからイテレータを生成するメソッドには3種類ある。
iter()
:イテレータはItemの不変参照を取得する。iter_mut()
:イテレータはItemの可変参照を取得するinto_iter()
:Itemの所有権がイテレータに移るIntoIterator
トレイトを実装した型はイテレータに変換できる。
into_iter()
でイテレータに変換する。
IntoIterator
トレイトを実装した型は for
で使用することもできる。
let values = vec![1, 2, 3, 4, 5];
for x in values {
println!("{}", x);
}
Vec
は IntoIterator
を実装しているため for
で使用できる。
内部的には values
のイテレータが作成され、そのイテレータでループが回っている。
https://doc.rust-lang.org/std/iter/index.html#for-loops-and-intoiterator
イテレータから別のイテレータを作成する関数をイテレータアダプタと呼ぶ。
具体的には map()
、take()
、filter()
などを指す。
イテレータは lazy である。
作成しただけでは何も起こらず、next()
を呼び出した時初めて動き出す。
map()
で指定したクロージャもイテレータを使用するまで呼び出されない。
map().collect()
とした時にクロージャは実行される。
コンパイル時のプロファイル
dev プロファイルと release プロファイルが存在する。
dev プロファイルは cargo build
コマンドで、
release プロファイルは cargo build --release
コマンドで使用される。
Boxはデータをヒープ領域に格納する。
let b = Box::new(5);
この場合、5
はヒープ領域に格納され、b
は格納箇所へのポインタを保持する。
Boxにどんなデータが入ろうとも、Boxのサイズは固定である。 Boxが持つのはポインタであり、データ自体はヒープ領域に格納されるからである。
下記のような構造体はコンパイルできない。
struct A {
a: Option<A>,
}
構造体が再帰的に自分自身と同じ型のインスタンスを保持する場合、その構造体のサイズは固定ではなくなるからである。
Boxで保持するようにすればサイズを固定にすることができる。
struct A {
a: Option<Box<A>>,
}
参照外し演算子(*
)の振る舞いを変更できる。
Derefトレイトを実装していない場合は & の参照外ししかできない。
ポインタからポインタの中身を取得することを「参照外し」と呼ぶ。
BoxはDerefを実装しており、*
をつけた時に参照先のデータを返すようになっている。
参照外し方強制(Deref coercion)
Deref coercion は、関数やメソッドの引数に対してRustが行う便利な機能です。
Deref coercion は、Derefトレイトを実装した型にのみ動作し、別の型の参照に変換します。
例えば String
は参照外しの際に &str
を返すようDerefトレイトを実装しているため、Deref coercion によって &String
を &str
に変換することが可能です。
impl ops::Deref for String {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
引数で値を受け取る時、Derefを実装した型の参照型は、参照外しをした後の型でも受け取ることが可能です。 その場合は自動的に参照外しが行われます。
fn main() {
let x:&str = "hello";
let y:String = String::from("hello");
test(x);
test(&y); // test(&(*y)); と同義であり、test(&(*(y.deref()))); と同義。
}
fn test(s: &str) {
println!("{}", s);
}
https://doc.rust-lang.org/book/ch15-03-drop.html
スマートポインターを使う上で重要となる2つ目のトレイトは Drop
です。
Drop
トレイトを使うことで、値がスコープの外に出るときの挙動をカスタマイズできます。
Drop
トレイトは任意の型に実装でき、実装する処理の中で、リソース(ファイルやネットワークなど)の解放を行うことができます。
Drop
トレイトをスマートポインタの文脈で紹介したのは、スマートポイントを実装する場合、ほぼ必ず Drop
トレイトを使用することになるからです。
例えば、Box<T>
はドロップされるときにBoxが指すヒープ領域の解放を行っています。
いくつかのプログラミング言語では、スマートポインタのインスタンスを使い終わる都度、メモリ(またはリソース)を解放するコードを呼び出す必要があります。 解放の呼び出しを忘れた場合、システムは負荷がかかりすぎてしまい、クラッシュしてしまう可能性があります。 Rustでは、値がスコープを抜ける時に特定のコードが実行されるように指定できます。 結果として、クリーンアップ用コードの設置に気を使う必要がなくなり、メモリーリークも抑止できます。
Drop
トレイトを実装し、値がスコープの外に出た時に実行するコードを指定します。
Drop
トレイトを実装する場合、self
への可変参照を受け取る drop
メソッドの実装が必要です。
Rustがいつ drop
を呼び出すかを確認するために、drop
メソッドで println!
を実行してみましょう。
リスト15-14では CustomSmartPointer
構造体を用意しています。
CustomSmartPointer
構造体はインスタンスがスコープの外に出た時に Dropping CustomSmartPointer!
と出力する機能を持っています。
この例では、Rustがいつ drop
メソッドを実行するかをデモンストレーションしています。
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
Drop
トレイトは自動でインクルードされているため、読み込み用の記述は必要ありません。
CustomSmartPointer
に Drop
トレイトを実装し、drop
メソッドで println!
を呼び出すようにしています。
drop
メソッドにはインスタンスがスコープの外に出た時に実行したい処理を記述します。
Rustがいつ drop
を呼び出すのかをデモンストレーションするため、drop
でいくつかのテキストを出力するようにしています。
main
メソッドでは CustomSmartPointer
のインスタンスを2つ作り、CustomSmartPointers created.
と出力しています。
main
メソッドが終了すると、 CustomSmartPointer
はスコープの外に出るため、drop
メソッドに記述した処理がRustによって呼び出され、最後のメッセージが出力されます。
明示的に drop
メソッドを呼び出す必要がないことに注目してください。
このプログラムを実行すると、次のように出力されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
Rustは自動的に drop
メソッドを呼び出しています。
変数は作成とは逆の順番でドロップされるます。
そのため、d
は c
より先にドロップされています。
この例は drop
がどのように動作するかを確認するためのものです。
実際にはメッセージの出力ではなく、その型に必要なクリーンアップコード(リソースの解放など)を記述することになるでしょう。
std::mem::drop
を使った早期ドロップ残念ながら、Drop
の機能を無効にすることは簡単ではありません。
Drop
トレイトのメリットは自動的に処理されることであり、これを無効にする必要は通常ありません。
しかし、場合によっては値を早めにクリーンアップしたいことがあります。
例えば、ロックを管理するスマートポインタを使用する時です。
ロックを解除するときに、同じスコープで他のコードがロックを取得できるように、強制的に drop
を実行したいというケースがあります。
Rustでは drop
メソッドを手動で実行することを許可していません。
スコープから外に出る前に強制的に drop
したい場合は、標準ライブラリが提供する std::mem::drop
関数を実行する必要があります。
リスト15-14を書き換えて main
関数の中で drop
メソッドを手動で呼び出すようにすると、コンパイルエラーとなります。
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
c.drop();
println!("CustomSmartPointer dropped before the end of main.");
}
以下のエラーメッセージが表示されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
--> src/main.rs:16:7
|
16 | c.drop();
| ^^^^ explicit destructor calls not allowed
error: aborting due to previous error
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example`.
To learn more, run the command again with --verbose.
このエラーメッセージは drop
の明示的な呼び出しが許可されていないことを示しています。
エラーメッセージでは「デストラクタ」という用語を使っています。
デストラクタはプログラミング用語であり、インスタンスをクリーンアップする関数のことです。
Rustにおいて、drop
関数はデストラクタの1つです。
std::mem::drop
関数は Drop
トレイトの drop
メソッドとは異なり、引数として drop
したい値を渡します。
この関数は自動的に読み込まれるため、そのまま使用できます。
リスト15-15 は以下のように書き換えることができます。
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}
コードを実行すると以下のように表示されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data some data!
CustomSmartPointer dropped before the end of main.
CustomSmartPointer created.
と CustomSmartPointer dropped before the end of main.
の間で drop
されていることがわかります。
https://doc.rust-lang.org/book/ch15-04-rc.html
所有権をどの変数が持っているのかは基本的には明白です。 しかし、一つの値に複数の所有者が存在するケースがあります。 例えば、グラフデータ構造(ノード(頂点)とエッジ(枝)で構成されるデータ構造)です。 複数のエッジが同じ一つのノードに繋がっている場合、概念的にはそのノードは複数のエッジに所有されていることになります。 ノードはそのノードを所有するすべてのエッジが無くなるまでクリーンアップされるべきではありません。
Rustでは Rc<T>
を使うことで所有権を複数の変数が持てるようになります。
RC は Reference Counting(参照カウンタ)の略です。
Rc<T>
型は変数がまだ使用されているかどうかを判断するために参照の個数を保持しています。
もし参照の個数が0であれば、クリーンアップできるということになります。
Rc<T>
をリビングにあるテレビに例えてみましょう。
誰か一人がテレビを見る時、電源を入れます。
別の人がリビングに入り、テレビを見ることも可能です。
最後の一人がリビングを出る時、テレビの電源は切られます。
もし誰かがテレビを見ているのに電源を切ってしまった場合、まだテレビを見ている人から文句を言われるでしょう。
プログラム中の複数の部分で読み込みたいデータをヒープ領域に格納し、コンパイル時にはデータがどこで一番最後に使われるか判断できない場合に Rc<T>
を使用します。
どこで最後に使われるか確定している場合は、その部分を所有者とし、コンパイル時に適用される通常の所有権ルールが有効となります。
注意事項として、Rc<T>
はシングルスレッドでのみ使用されます。
マルチスレッドでの参照カウンタについては16章で説明します。
Rc<T>
を使用したデータの共有リスト15-5のコンスリストの例に戻りましょう。
Box<T>
を使ってコンスリストを定義していました。
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
今回は特定のリストの所有権を共有する2つのリストを作成します。 概念的には図15-3のようになります。
まず5と10を持つリスト(a)を作成します。 そしてそのリストをもとに、3から始まるリスト(b)と4から始まるリスト(c)を作成します。 bとcのリストは5と10を持つリスト(a)に繋がっています。 言い換えると、bとcのリストは最初のリストを共有していることになります。
これを Box<T>
のリストを使って実装することはできません。
リスト15-17を見てください。
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
これはコンパイルエラーとなります。
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0382]: use of moved value: `a`
--> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: could not compile `cons-list`.
To learn more, run the command again with --verbose.
これは b
を作成した時点で a
の所有権は b
に移動しているためです。
その後 c
を作ろうとしても a
はすでに所有権が移動しているため、エラーとなります。
Cons
の定義を所有権ではなく参照を保持するように変更することも可能ですが、そうするとライフタイムパラメータを指定する必要があります。
ライフタイムパラメータを指定するようにすると、リスト内のすべての要素がリスト全体と同じだけ長生きすることを指定することになります。
また、借用チェッカーは let a = Cons(10, &Nil)
を許可しません。
一時的な Nil は参照を取得する前に drop
されてしまうからです。
代わりに、Box<T>
を Rc<T>
に変更します。
リスト15-18を見てください。
各 Cons
は値と、リストを指す Rc<T>
を保持します。
b
を作る時、a
の所有権を取るのではなく、a
をクローンしています。
それによって参照カウントが1から2になり、a
と b
で所有権を共有されます。
さらに c
を作るときにも参照カウントが増え、2から3になります。
このように、 Rc::clone
を呼ぶたびに参照カウンタは増加します。
そしてその参照カウンタが0になるまでクリーンアップは行われません。
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
Rc<T>
は自動的に読み込まれないため、 use
で読み込む必要があります。
main
関数では5と10を持つリストを作成し、それを Rc<List>
に入れたのち、a
に入れています。
そして b
と c
を作る時には Rc::clone
関数にa
の参照を渡して呼び出しています。
Rc::clone(&a)
ではなく a.clone()
でも良いですが、慣例としてここでは Rc::clone
を使っています。
これは a.clone()
だけだとディープコピーを行うのかどうか判断がつかないからです。
通常の型の clone
メソッドはディープコピーを行いますが、Rc::clone
(及び Rc<T>
のインスタンスの clone
メソッド)は参照カウンタを増加させるだけで、ディープコピーは行いません。
ディープコピーは時間がかかるため、ディープコピーを行うかどうかは重要です。
参照カウンタの増加には Rc::clone
を使うことで、見た目上、ディープコピーを行う clone
と区別できます。
コードのパフォーマンス低下の原因を探るとき、Rc::clone
は無視できるでしょう。
Rc<T>
のクローンa
の参照カウンタを確認できるようにリスト15-18を書き換えてみましょう。
リスト15-19では main
を変更し、 c
を内部スコープに入れています。
これによって c
がスコープの外に出た時の参照カウンタの変化が確認できます。
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
プログラム内の参照カウンタが変化する各ポイントで参照カウンタを出力しています。
参照カウンタは Rc::strong_count
関数で取得できます。
この関数の名前が count
ではなく strong_count
なのは、Rc<T>
型は weak_count
という関数も持っているからです。
weak_count
の使い方は Preventing Reference Cycles: Turning an Rc
このコードは以下のように出力します。
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.45s
Running `target/debug/cons-list`
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
最初の Rc<List>
の参照カウンタは1です。
その後、clone
が呼び出されるたびにカウントは1ずつ上がります。
c
がスコープの外に出るとカウントは1下がります。
参照カウンタを増加させるときは clone
関数を呼び出しますが、減少させるときには特に関数は呼び出しません。
Rc<T>
がスコープの外に出ると Drop
トレイトの実装によって自動的に参照カウンタが減少されます。
この例では b
と a
がスコープを出たときの参照カウンタは確認できませんが、カウンタは0となり、Rc<List>
はクリーンアップされます。
Rc<T>
を使うことで一つの値を複数の所有者が持つことができ、カウンタによって所有者が一人でもいる限り値は有効であることが保証されます。
Rc<T>
は内部で不変参照を取得するため、共有するデータは読み取りのみ可能です。
もし Rc<T>
が可変参照を許した場合、借用ルール違反となり、データの競合や不整合の温床となるでしょう。
次のセクションでは「interior mutability」パターンと RefCell<T>
型について説明します。
RefCell<T>
と Ref<T>
を合わせて使うことでこの不変性の制限に対応できます。
https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
RefCell<T>
と Interior Mutability パターン「Interior mutability」は、通常であれば借用ルールによって拒否される「不変参照のデータを変更する」ことを可能にするRustのデザインパターンです。
このパターンでは、データを変更するためにデータ構造の中で unsafe
コードを使ってRustのルールをねじ曲げています。
unsafe
については19章で説明します。
関連する unsafe
コードは安全なAPIによってラップされ、外側からは不変として使用されます。
「interior mutability」パターンを可能にする RefCell<T>
型に注目し、この概念について詳しくみていきましょう。
RefCell<T>
を使った、実行時の借用ルールの強制Rc<T>
とは違い、RefCell<T>
型はデータに対し単一の所有権しかありません。
では Box<T>
と RefCell<T>
の違いはなんでしょうか?
4章で学んだ借用ルールを思い出してみましょう。
Box<T>
と参照を使用する場合、借用ルールはコンパイル時に強制されます。
一方 RefCell<T>
を使用する場合、借用ルールは実行時に強制されます。
参照を使用するとき、借用ルールから外れた場合はコンパイルエラーが発生します。
RefCell<T>
の場合は借用ルールから外れた場合、コンパイルエラーは発生せず、実行時にパニックが発生し、終了します。
コンパイル時に借用ルールのチェックを行う利点は、エラーを早い段階で検知できることと、実行時にチェックを行わないためチェックにかかる負荷がなくなるということです。 そういった理由により、コンパイル時に借用ルールのチェックを行うことは大抵の場合はメリットとなります。
一方、実行時に借用ルールのチェックを行う利点は、コンパイル時のチェックでは禁止される特定のシナリオを許可できることです。 Rustコンパイラのような静的解析は基本的に保守的に動作します。 コードのいくつかのプロパティは分析時に検出できないことがあります。 最も有名な例は「Halting Problem」です。(https://ja.wikipedia.org/wiki/%E5%81%9C%E6%AD%A2%E6%80%A7%E5%95%8F%E9%A1%8C) https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%82%B9%E3%81%AE%E5%AE%9A%E7%90%86
いくつかの分析は不可能なので、コードが所有者ルールに則っていることをRustコンパイラが確認できなかった場合、そのプログラムは拒否されます。
もしRustが正しくないプログラムを許可した場合、ユーザーはそれを信頼できなくなります。
かといって、Rustが正しいプログラムを拒否した場合、壊滅的ではありませんが、プログラマーとしては不便です。
RefCell<T>
型はコンパイラが分析出来ないコードを記述する際に役立ちます。
Rc<T>
と同じように、RefCell<T>
はシングルスレッド上でのみ機能し、マルチスレッド上で使用するとコンパイルエラーとなります。
RefCell<T>
の機能をマルチスレッドプログラムで使用する方法は16章で説明します。
Box<T>
、Rc<T>
、RefCell<T>
のそれぞれの要約は以下の通りです。
Rc<T>
は一つのデータに対し、複数の所有者を存在させることができます。Box<T>
や RefCell<T>
は所有者は一人だけです。Box<T>
はコンパイル時の借用チェック(不変と可変の両方)が可能です。Rc<T>
はコンパイル時の借用チェック(不変のみ)が可能です。RefCell<T>
は実行時の借用チェック(不変と可変の両方)が可能です。RefCell<T>
は可変借用のチェックを実行時に行うことができるため、RefCell<T>
が不変であったとしても RefCell<T>
の中では値を書き換えることができます。不変な値の中で値を書き換えることを「interior mutability」パターンと呼びます。 「interior mutability」がどのような場面で便利で、どのように動作しているのかをみていきましょう。
借用ルールに則った場合、不変値を可変として借用することはできません。 例えば以下のコードはコンパイルできません。
fn main() {
let x = 5;
let y = &mut x;
}
これをコンパイル使用とすると以下のエラーが返ってきます。
$ cargo run
Compiling borrowing v0.1.0 (file:///projects/borrowing)
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> src/main.rs:3:13
|
2 | let x = 5;
| - help: consider changing this to be mutable: `mut x`
3 | let y = &mut x;
| ^^^^^^ cannot borrow as mutable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing`.
To learn more, run the command again with --verbose.
しかし、メソッド内でのみ値を変更できると便利という場面が存在します。
メソッド以外の部分では値は変更できないということです。
この「interior mutability」を実現する一つの方法は RefCell<T>
です。
しかし RefCell<T>
は借用ルールを完全に回避するものではありません。
コンパイラの借用チェッカーは「interior mutability」を許可し、代わりに実行時に借用ルールをチェックします。
もしルールに違反した場合、コンパイルエラーではなく panic!
となります。
RefCell<T>
の実践的な例を見ていきましょう。
テストダブルはテスト中に使用する代用の型を表す一般的なプログラミングの概念です。 モックオブジェクトはテストダブルの一つで、テストで正しい動作が行われたことをアサートできるようにします。
Rustは他の言語でいうようなオブジェクトいうものを持っておらず、他の言語が持っているようなモックオブジェクトの機能を持っていません。 ただし、モックオブジェクトと同じ目的を持つstructを作成することは確実にできます。
ここでは、値を追跡し、どのくらい最大値に近いかに基づいてメッセージを送信するライブラリを作成し、テストします。 このライブラリを使って、例えば割り当てに対するAPIのコール数を追跡することができます。
このライブラリは最大値にどれくらい近いかやどのようなメッセージになるべきかを追跡する機能だけを提供します。
このライブラリを使用するアプリケーションはメッセージを送るメカニズムを提供する必要があります。
アプリケーションはメッセージの設置、メールの送信、テキストメッセージの送信、などができます。
ライブラリはアプリケーションの詳細について知る必要はありません。
アプリケーション側では Manager
トレイトを実装します。
RFC803 「式が持つべき型」を記述する機能 https://stackoverflow.com/a/36390114
プロジェクトの作成
todo
プロジェクトの作成。 カレントディレクトリにtodo
ディレクトリが生成される。実行