Programming Rust 輪読 20181119
5章 参照
pp. 91-120
参照とは
所有権を持たないポインタ型
生存期間の決定には関わらない
というか、既に決められている生存期間に従う必要がある
参照先が生きている間にコトを済ませる
もし参照先が死んだ後に参照してしまうと危険になってしまうので、こういう強制をしている
借用とは
ある値に対する参照をつくること
「返す」必要がある
参照先が生きているうちに
例: メーカとその代表作のテーブル
次のように型を定義してみる
code:テーブルの型定義(rust)
use std::collections::HashMap;
type Table = HashMap<String, Vec<String>>;
表示する関数を作って、表示させてみる
次のようなコードが書けるしちゃんと動く
code:main.rs
use std::collections::HashMap;
type Table = HashMap<String, Vec<String>>;
fn show(table: Table) {
for (artist, works) in table {
println!("works by {}:", artist);
for work in works {
println!(" {}", work);
}
}
}
fn main() {
let mut table = Table::new();
table.insert("Google".to_string(),
vec!["Pixel Slate".to_string(),
"Pixel 3".to_string()]);
table.insert("Apple".to_string(),
vec!["iPhone X".to_string(),
"iPad Pro".to_string()]);
table.insert("HP".to_string(),
vec!["Spectre x360".to_string(),
"Elite x3".to_string()]);
show(table);
}
code:結果(txt)
works by Apple:
iPhone X
iPad Pro
works by Google:
Pixel Slate
Pixel 3
works by HP:
Spectre x360
Elite x3
が、これは意外とよくない
HashMap 型は Copy型ではない
のに、関数呼び出しでそのまま使っている
所有権は、当然、showへ移動してしまう
mainは所有しなくなり、mainのtableは未定義になる
この後にtableを使おうとすると怒られる
code:一部追加(rust)
// ...
fn main() {
// ...
show(table);
}
code:コンパイルエラー(txt)
errorE0382: use of moved value: table --> src/main.rs:25:14
|
24 | show(table);
| ----- value moved here
25 | assert_eq!(table"Google"0, "Picel Slate"); | ^^^^^ value used here after move
|
= note: move occurs because table has type std::collections::HashMap<std::string::String, std::vec::Vec<std::string::String>>, which does not implement the Copy trait
showの定義を見直すと、
外側のforが、ハッシュテーブルの所有権を取る
内側のforも、ベクタの所有権を取る
使う
ベクタの所有のスコープが死ぬ
テーブルの所有のスコープが死ぬ
データ構造を完全に破壊してしまったわけだ。ありがとう、Rust!
出力しただけでデータを破壊してしまうのは困る
参照で解決する
所有権には影響しないまま、値にアクセスすることが可能
参照には 2種類ある:
共有参照 (shared reference)
参照先を
読むことができる
変更することはできない
値eに対する共有参照: &e と書く
eの型がTなら、&eの型は&T
「ref T」と発音する
Copy型
可変参照 (mutable reference)
参照先を
読むことができる
変更することもできる
値eに対する可変参照: &mut e と書く
eの型がTなら、&eの型は&mut T
「ref mute T」と発音する
Copy型ではない
これらは、複数読み出し、単一書き込み というルールの強制のためにある
参照を使って、showを定義しなおしてみる
まず、mainでの呼び出し部分に参照を使う
code:呼び出し(rust)
show(&table);
次に、showの引数の型も参照にする
引数に&をつけるだけ
code:参照を使ったshow(rust)
fn show(table: &Table) {
for (artist, works) in table {
println!("works by {}:", artist);
for work in works {
println!(" {}", work);
}
}
}
上の修正では、showの内容は何も変えていない
実は、HashMapの共有参照に対する繰り返しは、個々の共有参照をとるように定義されている
artist, worksは、
元の版ではString, Vec<String>だったが、
今の版では&String, &Vec<String>になった
同様に、Vecの共有参照に対する繰り返しでも、
workは、
元の版ではStringだったが、
今の版では&Stringになった
これで、データ構造が破壊されることなく、mainのtableはデータを持ったままにできた
ここで、可変参照を使ってデータを変える例を見てみる
例: メーカごと、作品を辞書順に並べ替える関数
&mut をつける
code:sort_works(rust)
fn sort_works(table: &mut Table) {
for (_artist, works) in table {
works.sort();
}
}
code:呼び出し(rust)
sort_works(&mut table);
これで、sort_worksは、tableを変更できる
works.sort()で辞書順にソートされる
mainのtableが実体なので、そちらも変更される
mainからshowに渡すときにはソート済み
辞書順に並んで表示される
code:結果(txt)
works by Google:
Pixel 3
Pixel Slate
works by HP:
Elite x3
Spectre x360
works by Apple:
iPad Pro
iPhone X
ところで
値渡し (pass by value) とは
値の所有権を移動するような方法で、値を関数へ渡すこと
参照渡し (pass by reference) とは
値の参照を関数へ渡すこと
今の例では、値渡しから参照渡しに書き換えた、ということになる
5.1 値としての参照
p.95〜
5.1.1 Rustの参照 vs C++の参照
C++ は
参照は、変換で暗黙に作られる
参照解決も暗黙に行われる
Rust は
参照は、&演算子、あるいは&mut演算子で明示的に作られる
参照解決は*演算子で明示的に行われる
code:C++(c++)
int x = 10;
int &r = x; // 参照が初期化で暗黙に作られる
assert(r == 10); // rが暗黙に参照解決される(xの値が取り出される)
r = 20; // rが暗黙に参照解決される(xの値が20になる)(rはxの参照のまま)
code:Rust(rust)
// 共有参照
let x = 10;
let r = &x; // 共有参照を明示的に作る
assert!(*r == 10); // 参照解決も明示的に行う
// 可変参照
let mut y = 32;
let m = &mut y; // 可変参照を明示的に作る
*m += 32; // 参照解決を明示的に行い、yの値を変更する
assert(*m == 64); // 明示的にyの値を見る
ただ、さっきのメーカのテーブルの例では、*は使っていない
実は、 .演算子が、左オペランドの参照を暗黙に解決する(必要に応じて)
参照解決、めちゃめちゃ使われるので
メソッド呼び出しでもそうなる
code:_(rust)
v.sort(); // これと
(&mut v).sort(); // これは同じ
5.1.2 参照の代入
参照に代入すると、参照は、新しい値を指すようになる
以下の例では、bが真のとき、rはyへの参照になる
C++ とは挙動が違う
code:参照への代入(rust)
let x = 10;
let y = 20;
let mut r = &x; // ここではxへの参照
if b {
r = &y; // ここでyへの参照になる
}
assert!(*r == 10 || *r == 20);
5.1.3 参照への参照
参照への参照が許されている
何段階か参照をネストしてみる
code:参照への参照(rust)
struct Point { x: i32, y: i32 }
let point: Point = Point { x: 1000, y: 729 };
let r: &Point = &point;
let rr: &&Point = &r;
let rrr: &&&Point = &rr;
assert_eq!(rrr.y, 729); // .演算子が3段の参照を全て解決して値を取り出した
.演算子は、何弾でも参照解決を行って、目的の値を取り出す
上ではわかりやすさのために型を書いているが、もちろん省略可
メモリ上の参照はこうなっている
単に参照に対して参照がある状態
code:参照のネストのメモリ上の状態(txt)
point
/---^---\
x y r rr rrr 変数名
(1) (2) (9) (17) (25) アドレス番号的な
-----------------------------------------------------
| 1000 | 729 | ... | (1) | ... | (9) | ... | (17) | スタックフレーム
-----------------------------------------------------
5.1.4 参照の比較
.演算子と同様に、比較演算子も、何段でも参照解決する
双方のオペランドが同じ型である必要がある
code:参照の比較(rust)
let rx = &x;
let ry = &y;
let rrx = ℞
let rry = &ry;
assert!(rrx <= rry);
assert!(rrx == rry);
assert!(rx == rrx); // 失敗
code:結果(txt)
errorE0277: the trait bound {integer}: std::cmp::PartialEq<&{integer}> is not satisfied --> src/main.rs:43:14
|
43 | assert!(rx == rrx);
| ^^ can't compare {integer} with &{integer}
|
= help: the trait std::cmp::PartialEq<&{integer}> is not implemented for {integer}
= note: required because of the requirements on the impl of std::cmp::PartialEq<&&{integer}> for &{integer}
つまり両方を同時に 1段ずつ参照解決していっているってこと?
ここで、参照が指すメモリが同じかどうか、を知りたい場合、std::ptr::eqを使えばよい
code:アドレスでの比較(rust)
assert!(rx == ry); // 解決された後の値は同じだが
assert!(!std::ptr::eq(rx, ry)); // 指すメモリは違う
let rx2 = &x;
assert!(std::ptr::eq(rx, rx2)); // これは指すメモリも同じ
5.1.5 参照はヌルにならない
Rust の参照はnullにならない
CのNULL、C++のnullptrにあたるものが無い
CやC++では、それらを「存在しないこと」を表す値としてよく使う
参照のデフォルト初期値は存在しない
そもそも変数は初期化されるまでは使えない
整数から参照へ変換もできない(unsafeなら別だが)
Rust では、存在するかわからないものは Option<&T>を使う
None か Some(r)をとる (値rの型は&T)
使う前にNoneかどうかチェックする必要あり
これを使えば、C/C++の場合と同等の効率を得るし、
より安全
5.1.6 任意の式への参照の借用
(C/C++では&演算子が使える式の種類は限られているが)
Rust では任意の式の値に対して参照を借用できる
code:参照の例(rs)
fn factorial(n: usize) -> usize {
(1..n+1).fold(1, |a, b| a * b)
}
let r = &factorial(6);
assert_eq!(r + &1009, 1729);
数値演算子は、参照を 1段だけ解決する
r が解決されている
&1009は多分型を合わせているんだけど、この場合は双方の型が違っても解決してくれて動く
r + 1009 とか *r + &1009 とか
参考↓総ざらい
上の例の参照のパターン総ざらい:
36パターンが、どういうエラーになるか
factorial()に&/&mutつけるか否か
assert_eq!のrに*つけるか否か
assert_eq!の1009に
&/&mutつけるか否か
*つけるか否か
長いので gist に貼りましたが斜めに読んでください
ちなみに 36パターンは Rust で生成したけど Rust はこういうことをするための言語ではなさそうな気がする
今回のような、関数呼び出しの場合などでは、
式の値を保持する無名の変数が作られる
参照は、それを指す
無名変数の生存期間は、参照の使い方に依る
変数・構造体・配列など、値の一部としてすぐに代入する場合
その変数(・構造体・配列)の生存期間と同じになる
上の例では、rが生きているうちは無名変数も生きる
それ以外の場合
それを包む文の終わりまでになる
上の例では、1009を指すために作られた無名変数があり、
assert_eq!文が終了すると死ぬ
5.1.7 スライスとトレイトとオブジェクトへの参照
ここまでの参照は、単なるアドレスだったが、そうでないものが存在する
ファットポインタという
以下の 2ワードで構成される:
値へのアドレス
その値を使うために必要な情報を持つワード
ファットポインタには 2種類ある
スライスへの参照
2ワード:
スライスの開始点のアドレス
長さ
トレイトオブジェクト
特定のトレイトを実装した値、への参照 (11章に詳しい)
2ワード: トレイトのメソッド実行のために持つ
値のアドレス
その値に対応するトレイトの実装
情報は少し多いが、参照としての振る舞いは今までと同じ
5.2 参照の安全性
p.99〜
Rust がどうやって参照を制御しているかを見てみる
実際にエラーを起こしてみる
5.2.1 ローカル変数の借用
ローカル変数の参照を借用して、
その変数のスコープ外に持ち出してみる
code:簡単なエラーの例(rust)
fn main() {
let r;
{
let x = 1;
r = &x;
}
assert_eq!(*r, 1); // 参照先のxがもう死んでいる
}
code:コンパイルエラー(txt)
errorE0597: x does not live long enough --> src/main.rs:5:10
|
5 | r = &x;
| ^ borrowed value does not live long enough
6 | }
| - x dropped here while still borrowed
7 | assert_eq!(*r, 1); // xがもう死んでいるのでだめ
8 | }
| - borrowed value needs to live until here
Rust がどうやってダングリングポインタの可能性を見つけるか見てみる
まず、参照先の生存期間を見る
今回ではx
これが参照に許される最大の生存期間となる
参照は、この中で生まれ、この中で死ぬ必要がある
code:参照先の生存期間(rust)
fn main() {
let r;
{
let x = 1;
// ========== xの生存期間ここから
// ...
r = &x;
// ...
// ========== xの生存期間ここまで
}
assert_eq!(*r, 1);
}
次に、参照が格納される変数の生存期間を見る
今回ではr
初期化されてからスコープを外れるまで
これが参照に許される最小の生存期間となる
参照は、この範囲は最低でも生きていないといけない
code:参照先の生存期間(rust)
fn main() {
let r;
{
let x = 1;
// ...
r = &x;
// ---------- rの生存期間ここから
// ...
}
assert_eq!(*r, 1);
// ------------ rの生存期間ここまで
}
最大と最小が決まったので、関係を見る
最大が最小を包含する必要が有る
code:参照先の生存期間(rust)
fn main() {
let r;
{
let x = 1;
// ========== MAX xの生存期間ここから =====\\ // ... ||
// ---------- min rの生存期間ここから --\ || r = &x; // | ||
// ... | ||
// ========== MAX xの生存期間ここまで =====// } // |
assert_eq!(*r, 1); // |
// ------------ min rの生存期間ここまで --/ }
最小ををカバーして、かつ、最大の中に入るような生存期間は存在しない
のでおかしいことがわかる
ちなみに実はassert_eq!がなくても同じように怒られる
rが生きたままxが死ぬのは同じなので
こうするとうまく行く
最小をカバーし、最大の中に入る生存期間が存在する
code:うまく行く(rust)
fn main() {
let x = 1;
// ============ MAX xの生存期間ここから =====\\ { // ||
// ---------- min rの生存期間ここから --\ || let r = &x; // | ||
// ... | ||
assert_eq!(*r, 1); // | ||
// ... | ||
// ---------- min rの生存期間ここまで --/ || } // ||
// ============ MAX xの生存期間ここまで =====// }
5.2.2 仮引数として参照を受け取る場合
Rust は、参照を関数に渡すとき、
関数が参照を安全に使ってくれることを保証する
グローバル変数の場合を考える
code:コンパイルできない(rs)
// いくつか問題があるのでコンパイルできない
static mut STASH: &i32;
fn f(p: &i32) {
STASH = p;
}
問題とは:
static変数は初期化が必要
可変なstatic変数は、スレッド安全ではないので、unsafeブロックに入れる必要がある
すべてのスレッドがいつでもアクセス可能なので
まあ今確認したいことではないので、とりあえず解決してみる
code:とりあえず直した(rs)
static mut STASH: &i32 = &128;
fn f(p: &i32) {
unsafe {
STASH = p;
}
}
code:ここで出るコンパイルエラー
errorE0312: lifetime of reference outlives lifetime of borrowed content... --> src/main.rs:5:13
|
5 | STASH = p;
| ^
|
= note: ...the reference is valid for the static lifetime...
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the function body at 3:1 --> src/main.rs:3:1
|
3 | / fn f(p: &i32) {
4 | | unsafe {
5 | | STASH = p;
6 | | }
7 | | }
| |_^
これで怒られていることを理解するために、関数fのシグネチャを明示してみる
code:fの明示(rs)
fn f<'a>(p: &'a i32) { ... }
生存期間'a (「tick A」と発音する):
fの生存期間パラメータ
<'a>: 「任意の生存期間'aに対して」と読む
上の例では、fが「任意の生存期間'aを持つi32への参照」を引数にとる
'aは、「fを呼び出した部分を包含する任意の期間」になる
対して、STASHは、グローバル変数なので、ブログラム全体を生存期間とする
staticの生存期間を「'static生存期間」と呼ぶ
'aは'staticより短い (STASHに代入したpが先に死ぬ)
ダングリングポインタとなり危険
引数を'staticにすればよい
code:'staticにした(rs)
static mut STASH: &i32 = &128;
fn f(p: &'static i32) {
unsafe {
STASH = p;
}
}
ダングリングポインタを生まない値のみを引数として受け取るようにする
例えば:
code:fに渡す例(rs)
static WORTH_STPOINTING_AT: i32 = 1000;
f(&WORTH_STPOINTING_AT);
逆に、'aを引数にとるような関数は、その作用として関数外に生き残らせることはない、とわかる
関数のシグネチャだけで、引数の扱いを知ることができる
5.2.3 参照を引数として渡す
関数シグネチャとその中身の関係を見たので、
関数呼び出し側との関係を見てみる
code:関数呼び出しとの関係(rs)
// 生存期間を明示してみる
fn g<'a>(p: &'a i32) {
println!("{}", *p);
}
let x = 10;
g(&x);
これはコンパイルが通る
呼び出しのときには、生存期間を指定する必要はない
定義で決める
上で使った関数fで同じようにやってみる
code:fでやってみる(rs)
fn f(p: &'static i32) {
println!("{}", *p);
}
let x = 10;
f(&x);
これは通らない
xは初期化からスコープを外れるまでしか生きない
fは'staticを要求している
満たせない
5.2.4 返り値としての参照
説明したいことは上と同じで、
参照を格納する変数が、
参照よりも長生きだと、
ダングリングポインタが生まれる危険がある
ということ
これが関数からの返り値でも起こる
渡したベクタ参照の要素の参照が返ってくる場合など
5.2.5 参照を含む構造体
参照型が他の型定義に含まれる場合 (構造体に参照を置く場合など)
生存期間を明示する必要がある
関数の引数と同様
code:コンパイルエラー(rs)
struct S {
r: &i32
}
code:こうすれば通る(rs)
struct S {
r: &'static i32
}
これだと強すぎる
いつでもアクセスできる生存期間は普通いらない
code:こうすればよい(rs)
struct S<'a> {
r: &'a i32
}
構造体の中に構造体を置く場合でも、同様に生存期間を明示する必要がある
code:構造体の中に構造体を置く(rs)
struct T<'a> {
s: S<'a>
}
5.2.6 個別の生存期間パラメータ
構造体がひとつだからといって、中の要素の生存期間もすべて同じにすると、不具合があったりする
複数使うことが可能
code:2つの参照をもつ構造体の定義(rs)
struct S<'a> {
x: &'a i32,
y: &'a i32
}
code:問題になるコード(rs)
let x = 10;
let r;
{
let y = 20;
{
let s = S { x: &x, y: &y };
r = s.x;
}
}
code:コンパイルエラー(txt)
errorE0597: y does not live long enough --> src/main.rs:12:30
|
12 | let s = S { x: &x, y: &y };
| ^ borrowed value does not live long enough
...
15 | }
| - y dropped here while still borrowed
16 | }
| - borrowed value needs to live until here
一見よさそうなのに怒られる
怒られ内容: 「yの生存期間が足りない」
構造体の定義で、Sのx, yどちらにも'aという生存期間をつけた
変数yが素早く死んでしまうので、s.yもつられて死んでしまう
同じ生存期間であるs.xもその時点までに死ななければならない
変数xの生存期間と食い違ってしまう
この場合、生存期間を個別に指定すればよい
'a, 'bはそれぞれ独立
code:生存期間を個別に指定(rs)
struct S<'a, 'b> {
x: &'a i32,
y: &'b i32
}
これは関数シグネチャでも同じ
複数の引数に個別の生存期間をあてる
5.2.7 生存期間パラメータの省略
生存期間パラメータは、自明なとき省略可能
例:
code:省略の例(rs)
struct S<'a, 'b> {
x: &'a i32,
y: &'b i32
}
fn sum_r_xy(r: &i32, s: S) -> i32 {
r + s.x + s.y
}
sum_r_xyのシグネチャを省略せずに書くと:
code:省略しない場合(rs)
fn sum_r_xy<'a, 'b, 'c>(r: &'a i32, s: S<'b, 'c>) -> i32
(生存期間パラメータを持つ型である)参照などを返す場合、
仮引数に生存期間が 1つしかないとき、全ての返り値の生存期間は同じと推測される
ので省略可能
code:仮引数の生存期間が1つ(rs)
fn first_third(point: &i32; 3) -> (&i32, &i32) { }
ちなみにシグネチャを明示すると、
code:シグネチャの明示(rs)
fn first_third<'a>(point: &'a i32; 3) -> (&'a i32, &'a i32) 仮引数に生存期間が複数あるとき、明示する必要がある
どれを優先するというルールもないので
ので省略不可能
ただし、
関数が何らかの型に対するメソッドで、self仮引数を参照で取る場合、
selfの生存期間を優先して、返す値すべての生存期間とする
ref: 9.5
code:selfを取る例(rs)
struct StringTable {
elements: Vec<String>,
}
impl StringTable {
fn find_by_prefix(&self, prefix: &str) -> Option<&String> {
for i in 0 .. self.elements.len() {
if self.elementsi.starts_with(prefix) { return Some(&self.elementsi); }
}
None
}
}
find_by_prefixメソッドのシグネチャを省略しない場合:
code:省略しない場合(rs)
fn find_by_prefix<'a, 'b>(&'a self, prefix: &'b str) -> Option<&'a String>
5.3 共有と変更
p.111〜
参照が入った変数がスコープから外れることでダングリングポインタができる例を見てきた
実は他にもダングリングポインタができる場合がある
例:
code:移動してダメになる例(rs)
let r = &v;
let aside = v; // ベクタ(の所有権)をasideに移動
同じスコープ内ではあるが、
所有権が移動し、vがみ初期化状態になる
ので、rはダングリングポインタとなる
まあそれはそう
こうすると通る
code:コンパイルが通る場合(rs)
{
let r = &v;
}
let aside = v;
ちなみに行の順序が変わっているが、
スコープを切らないとそれはそれで通らない
code:スコープを切らない場合(rs)
let r = &v;
let aside = v; // vが未定義になる
// ... // ここでrがダングリングポインタになりえる
r[0]の行は大丈夫でも、
rが死なないので、
ダングリングポインタを生む可能性が出るため
ちなみにvは未定義になるが、その状態で使わなければ許される(コンパイルが通る)
大惨事を引き起こす例は他にもある
例: スライスの要素を使ってベクタを拡張する関数
(実はよっぽど柔軟で最適化されたメソッドが標準ライブラリでベクタに用意されている)
(extend_from_slice)
code:大惨事を引き起こしうる例(rs)
fn extend(vec: &mut Vec<f64>, slice&f64) { for elt in slice {
vec.push(*elt);
}
}
code:使ってみる(rs)
let mut wave = Vec::new();
extend(&mut wave, &head); // ベクタで拡張
extend(&mut wave, &tail); // 配列で拡張
extend(&mut wave, &wave); // 自身で拡張
assert_eq!(wave, vec![0.0, 1.0, 0.0, -1.0,
0.0, 1.0, 0.0, -1.0]);
一見よさそう
が、実は問題がある
Rust 固有の問題ではない
ベクタに要素を追加するとき、
バッファの容量が足りなくなったら、
容量の大きい新しいバッファを確保しないといけない
waveが 4要素のバッファでできていて、
5つ目を追加しようとすると、
大きいバッファを取り直す必要がある、とする
取り直したら、元のバッファは解放されてしまう
実はsliceが元のバッファを参照し続けている
壊れる
Rust は、これに対してもコンパイルエラーを出す
code:コンパイルエラー
errorE0502: cannot borrow wave as immutable because it is also borrowed as mutable --> src/main.rs:17:22
|
17 | extend(&mut wave, &wave); // 自身で拡張
| ---- ^^^^- mutable borrow ends here
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
可変参照を借用しているのに、共有参照も同時に借用する、のは許されない
複数読み出し / 単一書き込みのためのルールとして、
共有アクセスは読み出しのみになる
共有参照が死ぬまでは、書き込めない
参照先から辿れるもの全て
該当の構造体の中身だけの可変参照を作る、とかもできない
共有参照ならいくつでも作れる
code:共有アクセス(rs)
let mut x = 10;
let r1 = &x;
let r2 = &x; // 共有参照は同時にいくつでも作れる
x += 10; // NG: 共有参照が生きているので、xの値は変更できない
let m = &mut x; // NG: 共有参照が生きているので、可変参照は作れない
let zx = x; // NG: 共有参照が生きているので、使えない
code:共有参照から再借用(rs)
let mut w = (107, 109);
let r = &w;
let r0 = &r.0; // 共有参照から辿れる値でも共有参照は作れる
let m1 = &mut r.1; // NG: 共有参照から辿れる値の可変参照は作れない
可変アクセスは排他アクセスになる
可変参照が死ぬまでは、その参照を通じてのみアクセス可能
読み書きOK
ただし、他のパスからのアクセスは不可能
参照先から辿れるもの全て読み書きNG
code:可変アクセス(rs)
let mut y = 20;
let m1 = &mut y;
let m2 = &mut y; // NG: 可変参照は同時に作れない
y += 10; // NG: 可変参照が生きているので、yの値を直接は変更できない
let r = &y; // NG: 可変参照が生きているので、共有参照は作れない
let zy = y; // NG: 可変参照が生きているので、使えない
code:可変参照から再借用(rs)
let mut v = (136, 139);
let m = &mut v;
let m0 = &mut m.0; // 可変参照から辿れる値なら可変参照は作れる
let r1 = &m.1; // 可変参照から辿れる値なら共有参照も作れる
v.1; // NG: 可変参照から辿れる値でないので読み書きできない
先の例では、
waveの可変参照を作っているので、
waveの共有参照は作れない
逆に、共有参照があるので、可変参照は作れない、と考えてもよい
ちなみに C のコードでは、この制約がない所為で、
参照は const だけど、
元の値を書き換えることは可能なので、
constの参照を解決すると値が変わっている、がありえる
constじゃないじゃん、となる
code:C(c)
int x = 42;
const int *p = &x;
assert(*p == 42);
x *= 2;
assert(*p == 84); // 値が変わっている
code:Rust(rs)
let mut x = 42;
let p = &x;
assert_eq!(*p, 42);
x *= 2; // NG: できない
assert_eq!(*p, 42); // 値は変わっていなくて便利
5.3.1 オブジェクトの海に立ち向かう
1990年代の、自動的なメモリ管理の興隆から、
すべてのプログラムの基本的な構造が、オブジェクトの海となった
めっちゃいっぱいあるオブジェクトが互いに参照しあっていて最悪、みたいな状態
まともに設計をしないとそうなる
メリットは、
最初は素早く書ける
手軽に変更できる
数年経てば完全に書き直せる!!!!!!!!!!!
最悪 (BGM: Highway To Hell - AC/DC)
https://www.youtube.com/watch?v=l482T0yNkeo
scrapboxってyoutube埋め込まれるのマジか
冷静に考えて、デメリットは、
テストも
改良も
コンポーネントを分離して考えるのも
大変だし難しい
Rust を使うと、強制的に、そうならないようにブレーキがかかる
相互参照のような状態は手間がかかるようになっている!!!!!
Rcのようなスマートポインタ型を使い、
内部可変性を用いる必要がある
基本的に一方向に流れるのを好む
歯を食いしばって設計をしろ、と書いてある
いつか苦しむのではなく今苦しむべきらしい
聞くは一時のみたいな
(17497文字 / 940行)