rust: Rustがんばろう 5日目
2025/02/11
はじめに
Rust を勉強することにした。 ようやく、話によく聞く「所有権(ownership)」の章だ。
所有権
所有権を理解する - The Rust Programming Language 日本語版
RAII
メモリ確保と同時に初期化し、メモリ解放と同時に終了処理をする、みたいな方式の名称、と読み取った。
コンストラクタで配列のメモリを確保し、デストラクタでそれを解放する、とか。
コンストラクタでファイルオープンし、デストラクタでファイルクローズする、とか。
C++ だとメモリ確保でコンストラクタが呼ばれ、解放でデストラクタが呼ばれるのでよく使われる。
C言語は、自分で実装していないことが勝手に行われることが少ないので RAII 的なことも行われない。 それもあってメモリリークが起こりやすい。 メモリの生存期間が目に見えるスコープ内だったら単体テストなどで見つけやすいけど、 そうもいっていられないこともある。
なので参照カウンタを使って誰も参照しなくなったら解放する、という管理をしたりするけど、
そもそもそれも自動でやらないので、長期で動かしてときどきカウンタを出力し、
本当にそんなに参照している人(?)がいるのか確認したりしてたけど、まあ面倒だし絶対に漏れが分かるとも言いがたい。
それならいっそのこと、定期的に再起動させた方が楽・・・みたいな。
何が言いたいかというと、C++ の方がいい!とはいわないけれど、ときどき使いたい機能はあるよね、ということだ。
namespace
とかほしい。
move と clone と reference
全部の型を見てないけど、いわゆるプリミティブ型はスタックにしかメモリ確保しないので代入はコピーになり、 それ以外の型は代入すると move になる、と読み取った。
- move はわかったけど引数で渡したときにも move になるんだ!
- 単に値を見せたいだけってこともあるだろうから、何か解決方法があるだろう
- Copy On Write みたいなことはできないのかな
- Cow in std::borrow - Rust
- “Clone On Write” と書いてあったがたぶん同じことだろう
あれ、これは OK なんだ?
fn main() {
let s = String::from("Hello, world!");
println!("{}", s);
println!("{}", s);
}
これはダメだった。
無条件で move になるわけではないということか。
fn main() {
let s = String::from("Hello, world!");
print(s);
print(s);
}
fn print(s: String) {
println!("{}", s);
}
・・・ちゃんと次のページに「参照」があった。
呼び出す方が参照にするだけでなく、受け取る方も参照しかダメなようになる。
なので、呼び出し型によって move だったり reference だったりということはない。
fn main() {
let s = String::from("Hello, world!");
print(&s);
print(&s);
}
fn print(s: &String) {
println!("{}", s);
}
reference に mut
を付けると編集権限を与えたようなものだ。
あらゆるところに mut
を付けないといけない。
fn main() {
let mut s = String::from("Hello, world!");
print(&mut s);
print(&mut s);
}
fn print(s: &mut String) {
s.push('!');
println!("{}", s);
}
reference の mut
は同じものを複数から指すことができない。
もうちょっと条件はあるようだが、実際にそういうことをしたくならないと必要性が思いつかないので良かろう。
let mut s = String::from("Hello, world!");
let mut r = &mut s;
let mut t = &mut s;
これも同じくダメだ。
let mut s = String::from("Hello, world!");
let mut r = &mut s;
print(&mut s);
参照をさらに参照すると &&
になった。
さらにそれを参照すると &&&
になったのでずっと続くのかもしれない。
fn main() {
let mut s: String = String::from("Hello, world!");
let r: &String = &s;
let t: &String = &s;
let u: &&String = &r;
print(&s);
print(&r);
print(&t);
print(&u);
s.push('!');
print(&s);
}
fn print(s: &String) {
println!("{}", s);
}
この節のタイトル「参照と借用(references and borrowing)」の「借用」はどこに出てきたんだっけ?
最初の方だった。
関数の引数に参照を取ることを借用と呼びます。
実引数と仮引数のような立場の違いですな。
スライス
所有権のない別のデータ型は、スライスです。
参照/借用とは別にってことね。
golang のスライスは可変配列と違って必ず何かの参照になる?
いや、たぶんそんなことはないと思う。
ただ String
だったものが &str
から部分的に参照されても「参照」扱いになるという説明だと読み取った。
ちなみに、以下のように書いて s.clear()
で関数が終わるとエラーにはならない。
この後ろで t
を参照しようとすると s.clear()
の行がエラーになる。
スコープ外になる s
に対して s.clear()
しても意味が無いので省略されたのか、文脈として使われないからエラーにならないのかは分からない。
たぶん後者だと思う。
let mut s: String = String::from("Happy Turn is Your Friend");
let t: &str = &s[..5];
println!("result: {}", t);
s.clear();
// println!("result: {}", t); ←コメント解除するとエラーが起きる
おわりに
move, clone, reference まではルールということでわかったのだが、スライスのところはまだだ。
まだ裏がありそうというか、参照の別形式の説明をしたいから出てきただけだよねぇ、という感じがするからだ。
まあ、あまり考えても仕方ない。