🎯 この記事の対象者
- Rust良いよね!
- でも所有権とか借用はやっぱり難しいよね!
- 変数の代入や、関数への値渡しか参照渡しかぐらいは知ってるよ!
- DBの排他制御もわかるよ!
そんな自分のための整理です。
何か誤りなどあれば、お気軽にコメントしてください。
私自身も探求中の未熟者なのでご容赦ください🙇
1.値渡し(ムーブ)= 所有権の移動 → 受け渡し元の役目終了
let name = String::from("一郎");
let new_name = name; // ⭕ 単純な代入
println!("{}",new_name);
// ★★★ エラー ★★★★★★★★★★★★★
println!("{}",name);
// ★★★ エラー ★★★★★★★★★★★★★
- String"一郎"の所有者はすでに new_name
- nameは String"一郎" の所有権をすでに手放してしまった
new_name = name;
代入したということは権利の一切合財をプレゼントしたということ
2.値渡し(ディープコピー)
let name = String::from("次郎");
let new_name = name.clone(); // ⭕ クローン
println!("{}",new_name);
println!("{}",name);
3.参照渡し(不変の借用 Immutable)= 共有ロック
let name = String::from("サブロー");
let new_name = &name; // ⭕ 「&」をつけて貸し出す
// DBで言うところの「共有ロック(Shared Lock)」
// nameというテーブルを「参照のみ可」でロックしている状態。
// ★★★ エラー ★★★★★★★★★★★★★
// ❌共有ロックがかかっている最中は、たとえ「所有者」でも書き換え禁止!
name = String::from("上書き")
// ★★★ エラー ★★★★★★★★★★★★★
// ★★★ エラー ★★★★★★★★★★★★★
// ❌参照しているだけのポインタに直接文字列をセットできるわけがない
new_name = String::from("エラー")
// ★★★ エラー ★★★★★★★★★★★★★
println!("{}",new_name);
println!("{}",name);
// ⭕ new_nameが終了し、共有ロックが開放されたら上書き可能!
name = String::from("上書き")
✅ 所有権の観点でのエラー
- new_nameは String"サブロー" の所有者ではない(所有者は name)
- new_nameは String"サブロー" のただの借用者
借用者には見る権利しか無い
✅ 型の観点でのエラー
- new_nameの型は Stringではない! &String(String型へのポインタ)
- ポインタへStringの実態を代入することは出来ない!
4.参照渡し(可変の借用 mutable 1つだけ編集許可 )= 排他ロック
// ✅ ポイント:書き換えてもらうなら、大元も mut が必要!
let mut name = String::from("士郎");
// --- 🔽 トランザクション開始(スコープに入る) 🔽 ---
{
// nameの編集権はnameが持っているでの編集できる
println!("{}", name);
name = String::from("山岡");
// 編集権を渡す(排他ロックする)
// ✅ ポイント:「書き換えていいよ」という契約(&mut)で渡す
let new_name = &mut name; // ⭕ &mut
// new_name へ編集権を渡しているので nameは編集できない
// ★★★ エラー ★★★★★★★★★★★★★
name = String::from("加藤");
// ★★★ エラー ★★★★★★★★★★★★★
// UPDATE実行(未コミット状態)
*new_name = String::from("上書き");
// ★★★ エラー ★★★★★★★★★★★★★
println!("{}", name);
// ★★★ エラー ★★★★★★★★★★★★★
// 🚨 注意:
// この処理があることで、new_nameがココまで生きている
// 逆にこの処理が無いと、RustがNLL(Non-Lexical Lifetimes)機能で
// 最後に new_nameが利用された場所で、排他ロックを開放してしまう。
// new_name が生きていると、上のprintlnが name を参照した時、
// new_nameが コミットして編集権を nameに返していない状態なので
// 「Dirty Read(ダーティリード)の危険あり!」とコンパイラに怒られます。
*new_name = String::from("さらに更新");
}
// --- 🔼 COMMIT & ロック解放(スコープを抜ける) 🔼 ---
// ここまで来れば排他ロックは解除されているので、
// 大元の `name` を安全に Read(SELECT)できます!
println!("{}", name); // 正常!「さらに更新」が出力される
✅ 編集権の観点でのエラー
- 編集権は1つの変数にだけ渡すことができる
- 編集権を渡したら、大元は編集できない
- 編集権を受け取った変数が、変更し未確定の間は大元を参照できない(ダーティーリード)
- 編集権を受け取った変数が開放される(コミット)と、大元の値を安全に取得できる
DBの排他制御に置き換えるとわかりやすい!
5.参照渡し(可変の借用の誤った書き方① mutable)
let name = String::from("五郎");
// new_name(ポインタの箱自体)はmutだけど、
// 中に入っている nameがmutじゃない
let mut new_name = &name;
// ★★★ エラー ★★★★★★★★★★★★★
*new_name = String::from("山田");
// ★★★ エラー ★★★★★★★★★★★★★
✅ 間違いポイント
- そもそも大元の name が mut ではないので、どんなに頑張っても書き換える権利は発生しません。
6.参照渡し(可変の借用の誤った書き方② mutable)
let mut name = String::from("六郎");
// nameもnew_name(ポインタの箱自体)もmutだけど、
// mutableとして代入していない
let mut new_name = &name; // ❌ 「&」は参照の借用
// ★★★ エラー ★★★★★★★★★★★★★
*new_name = String::from("山田");
// ★★★ エラー ★★★★★★★★★★★★★
// ⭕ (おまけ)ちなみに、これなら成功します
let mut new_name = &mut name; // ⭕ &mut は編集権付き借用
*new_name = String::from("山田");
✅ 間違いポイント
- 借りる時の契約が
= &name(不変参照)なら、中身をいじることは許されない - 借りるときの契約を
= &mut name(可変参照)にする必要がある
7.関数への引数(所有権を渡す)
fn test(s: String) {
println!("関数内出力:{0}",s);
}
fn main() {
let name = String::from("七郎");
test(name);
// nameは関数の引数へ所有権を渡してしまった。
// すでに所有権が無い。別の変数へ代入したのと同じ
// ★★★ エラー ★★★★★★★★★★★★★
println!("{}",name);
// ★★★ エラー ★★★★★★★★★★★★★
}
8.関数への引数(不変の借用 Immutable)
fn test(s: &String) {
println!("関数内出力:{}", s);
}
fn main() {
let name = String::from("八郎");
test(&name); // ⭕ 「&」をつけて貸し出す
println!("返却後:{}", name); // 貸しただけなので、戻ってきている
}
9.関数への引数(可変の借用 mutable)
fn test(s: &mut String) {
*s = String::from("関数内で上書き");
}
fn main() {
// ✅ ポイント:書き換えてもらうなら、大元も mut が必要!
let mut name = String::from("九郎");
println!("テスト実行前:{}", name);
// ✅ ポイント:「書き換えていいよ」という契約(&mut)で渡す
test(&mut name);
println!("テスト実行後:{}", name);
}
- テスト実行前 九郎
- テスト実行後 関数内で上書き
✅ まとめ
Rust良いよね!
なぜこんな小難しいルールがあるんだ!と僕は全く思いませんでした。
こういう制限が最初から有れば(コンパイラが弾いてくれれば)、安全なのにと思っていたことが実現してるのでRustに出会えてすごく嬉しいです!