Rustの所有権のメモ。
所有権は値と変数に関わる話だが、Copyトレイトを実装している型については関係ない。
つまり、基本データ型(整数や浮動小数など)はCopyトレイトが実装されているので、所有権を気にする必要は無い。
所有権が関わるのは、以下のような、値の移送を行う箇所。
このような場合に値の移送を行う必要があるが、Copyトレイトが実装されている型の場合は、単純に値がコピー(複製)できる。(コピーセマンティクスと言う)
Copyトレイトが実装されていない型については、所有権の移動(ムーブ)を伴う。(ムーブセマンティクスと言う)
ただし、常に所有権が移動してしまうのも困るので、所有権が移動しないようにする方法はある。→借用
自分はRust初心者なので間違っているかもしれないが、Copyトレイトが実装されていない場合に所有権が関係することについて、以下のように理解している。
なお、所有権はコンパイル時にチェックされるものであって、コンパイル結果の実行コードの中に(所有権のフラグのような情報が)含まれているわけではない。
所有権の基本的な考え方は単純。
値を変数に代入すると、変数がその値を所有する(所有権を持つ)。
所有権を持った変数がスコープから外れると、値が破棄される。(デストラクター(Dropトレイトのdropメソッド)が呼ばれる)
{ // スコープの始まり let s = "abc"; // sが"abc"の所有権を持つ println!("s = {}", s); } // スコープが終わるので、sが所有権を持っている"abc"は破棄される
これは、Javaのtry-with-resourcesと似ていると思う。
// Java try (var resource = 〜) { // tryでresource(変数)を宣言する 〜 } // tryブロックが終わるとき、tryで宣言された変数に対してcloseメソッドが呼ばれる(リソースを解放する)
Javaではtry文で指定した変数しか対象にならないが、
Rustでは(Copyトレイトが実装されていない型の)全ての変数に対して自動的に対象になるという感じ。
所有権がtry-with-resourcesより優れているのは、関数から値を返す場合(ブロックから抜けるが値を返したい場合)も所有権を移転することで、ブロックに囚われない管理が出来るところかな。[2025-07-05]
ひとつの値(インスタンス)に対し、所有権を持つ変数は常にひとつのみである。
別の変数へ代入を行うと、所有権はその変数に移る。
関数の引数に渡すと、所有権はその引数に移る。
所有権を失った変数は使用できなくなる。(使うようなコーディングをするとコンパイルエラーになる)
{
let s1 = String::from("abc"); // s1が所有権を持つ
let s2 = s1; // 所有権がs2に移る。以降、s1は使えない
//× println!("s1 = {}", s1); // s1は所有権を失っているので使えない。使おうとするとコンパイルエラーになる
println!("s2 = {}", s2);
} // スコープが終わるとき、s2が所有権を持っている値は破棄される。s1は所有権を持っていないので何もしない
別の見方をすると、「所有権を持つ変数は、値を破棄する責務がある」と言えるだろう。
所有権を移すと、破棄する責務を渡したということになる。
変数から別の変数へ代入するのはあまりしないような気がするが、
複数の関数やメソッドに引数として渡すことはよくある。
しかし、最初の関数呼び出しの引数で所有権が移動してしまうと、次の関数に渡せなくなってしまう!
fn func1(s: String) { println!("func1.s = {}", s); }
let s = String::from("abc");
func1(s); // 所有権がfunc1の引数に移る。以降、sは使えない
func2(s); // sは所有権を失っているので使えない。コンパイルエラーになる
こういった場合に備えて、所有権を渡さずに残したまま借用するという方法がある。
関数やメソッドを呼び出す際に引数に所有権を渡したくない場合は、参照で渡すという方法がある。
関数やメソッドで引数を定義する際に、型の前に「&」を付けると参照で受け取ることになる。
fn func1(s: &String) { println!("func1.s = {}", s); }
関数の引数を参照で受け取ることを借用と呼ぶ。
所有権は元の変数に残したまま、値だけを借りるというニュアンスだろうか。
参照を受け取る引数に変数を渡すには、変数の前に「&」を付ける。
let s = String::from("abc"); func1(&s); // func1には参照が渡され、所有権はsに残る func2(&s); // func2には参照が渡され、所有権はsに残る
参照(「&」付き)で受け取っても、大抵は「&」無しと同様に扱えるが、「*
」を付けると参照を外すことが出来る。[2024-09-19]
fn func(s: String, r: &String) { println!("r.len = {}", r.len()); // &付きの変数でも、&無しと同様に扱える println!("s.len = {}", s.len()); println!("*r.len = {}", (*r).len()); // 参照外し println!("*s.len = {}", (*s).len()); // &付きでないのに、これも通る^^; }
例えばSomeに参照を渡すと、unwrapしたものも参照になる。こういった場合に参照を外すには「*
」を付ける。
let s = Some(&123); // sの型は Option<&i32> let r = s.unwrap(); // rの型は &i32 let n = *r; // nの型は i32
Rustの参照には、不変参照(&)と可変参照(&mut)がある。[2025-07-06]
まずは、不変参照は(値を変更できず)取得するのみの参照で、可変参照は値を変更できる参照と思っておけばいい。
(→不変参照で変更できるパターン)
ただし、可変参照はただ単に値が変更できるだけの参照と思ってはいけない。
可変参照には、不変参照には無い重要なルールがある。
ひとつの値につき、可変参照は同時に1つしか持てないという制約である。
(Rustでプログラムを書いていると、この制約に引っかかってハマることがたまによくあるんだよなぁ)
Rustの参照には、以下のルールがある。
ルール | 例 | 備考 |
---|---|---|
不変参照はいくつも持てる |
let s = String::from("abc"); let r1 = &s; let r2 = &s; println!("r1={}", r1); println!("r2={}", r2); |
|
可変参照はひとつしか持てない |
let mut s = String::from("abc"); let r1 = &mut s; let r2 = &mut s; // コンパイルエラー println!("r1={}", r1); println!("r2={}", r2); |
可変参照を作る場合、対象の値はmutの必要がある。 |
不変参照がある場合、可変参照を作れない |
let mut s = String::from("abc"); let r1 = &s; // 不変参照 let r2 = &mut s; // コンパイルエラー println!("r1={}", r1); println!("r2={}", r2); |
|
可変参照がある場合、不変参照を作れない |
let mut s = String::from("abc"); let r1 = &mut s; // 可変参照 let r2 = &s; // コンパイルエラー println!("r1={}", r1); println!("r2={}", r2); |
|
可変参照を不変参照として使える |
fn ref_function(value: &String) { println!("ref_function={}", value); } fn mut_function(value: &mut String) { println!("mut_function={}", value); } fn main() { let mut s = String::from("abc"); let r = &mut s; mut_function(r); ref_function(r); // 不変参照に渡す } |
ここでは主に変数へ代入する例を挙げたが、実際には、参照を引数に取るメソッドを呼ぶことが多いと思う。
(引数が「&」なメソッドを呼んだり、引数が「&mut」なメソッドを呼んだりする)
不変参照中に可変参照を作れない、もう少し複雑な例。
#[derive(Debug)] struct MyStruct { value: String, } impl MyStruct { fn new(value: &str) -> MyStruct { MyStruct { value: value.to_string(), } } fn value(&self) -> &String { &self.value } }
fn main() {
let mut s = MyStruct::new("abc");
let value = s.value();
let r = &mut s; // コンパイルエラー: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("value={}", value);
println!("{:?}", r);
}
この例では、&mut sで「既に借用(borrow)されている」というコンパイルエラーになる。
sの参照(借用)は他に無いように見えるのだが…。
実は、s.value()で返されている&Stringは、sの借用として扱われているようだ。
s.value()の実際の値は、MyStructが所有権を持っており、&Stringはそれを貸し出している形になる。
sの内部の値が貸し出されていると、s自体が借用されているという扱いになるのだと思われる。
処理の順序を変えて、参照されなくなれば、コンパイルが通る。
(いつも順序を変えられるとは限らないんだけど…)
fn main() { let mut s = MyStruct::new("abc"); let value = s.value(); println!("value={}", value); // これ以降、value(&String)は使われない let r = &mut s; // OK println!("{:?}", r); }
もしくは、valueのクローンを作れば(複製してしまえば)参照が無くなる。
(クローンするのは、場合によってはコストが大きいので、あまりやりたくないけど…(RcやArcならいい))
fn main() { let mut s = MyStruct::new("abc"); let value = s.value().clone(); // クローンを作ることで、valueの型はStringになる(&Stringでなくなる) let r = &mut s; // OK println!("value={}", value); println!("{:?}", r); }
LLM(いわゆるAI)にalso borrowedのエラーの解決方法を聞くと、波括弧でブロックを作る方法を提案されることがあるが、
ブロックを作ってスコープを制限したつもりでも、ブロックの外まで参照を出していたら、参照が無くなるわけではないので解決にならない。
fn main() { let mut s = MyStruct::new("abc"); let value = { s.value() }; // ブロック化しても、ブロックの外に参照(&String)を出しているので無意味 let r = &mut s; // コンパイルエラー println!("value={}", value); println!("{:?}", r); }
基本的には、不変参照(&)は値を変更できず取得するのみの参照で、可変参照(&mut)は値を変更できる参照である。
しかしマルチスレッドプログラミングをしていると、どうも単純にそうは言い切れないように思えてきた。
とでも言う方が正確な気がする。
struct MyStruct { value: i32, } impl MyStruct { fn new(value: i32) -> MyStruct { MyStruct { value } } fn value(&self) -> i32 { self.value } fn increment(&mut self) { self.value += 1; } }
fn main() { let mut s = MyStruct::new(1); println!("{}", s.value()); s.increment(); println!("{}", s.value()); }
MyStructで値を保持している。
value()は保持している値を返すだけなので(MyStruct(self)のフィールドを変更しないので)不変参照(&self)であり、
increment()は保持している値(MyStruct(self)のフィールド)を変更するので可変参照(&mut self)になる。
ところが、構造体のフィールドをi32でなくAtomicI32(原子的更新されるi32)にすると、可変参照の必要が無くなる。
use std::sync::atomic::{AtomicI32, Ordering};
struct MyStruct { value: AtomicI32, } impl MyStruct { fn new(value: i32) -> MyStruct { MyStruct { value: AtomicI32::new(value), } } fn value(&self) -> i32 { self.value.load(Ordering::SeqCst) } fn increment(&self) { // 不変参照でいい self.value.fetch_add(1, Ordering::SeqCst); } }
fn main() { let s = MyStruct::new(1); // mutは不要 println!("{}", s.value()); s.increment(); println!("{}", s.value()); }
このincrement()は、挙動としてはMyStructのフィールドの値を変更しているんだけど、可変参照(&mut)にする必要は無い。
valueフィールド自体を変更しているわけでもなく、AtomicI32の可変参照メソッドを呼んでいるわけでもない(fetch_add()は不変参照(&self)のメソッドである)からだ。
フィールドをAtomicにする例と同様に、Mutex(排他制御する構造体)でも不変参照で変更できる。
use std::sync::Mutex;
struct MyStruct { value: Mutex<i32>, } impl MyStruct { fn new(value: i32) -> MyStruct { MyStruct { value: Mutex::new(value), } } fn value(&self) -> i32 { let value = self.value.lock().unwrap(); *value } fn increment(&self) { // 不変参照でいい let mut value = self.value.lock().unwrap(); // ロックして値を変更する場合は、ロックされた変数をmutにする *value += 1; } }
fn main() { let s = MyStruct::new(1); // mutは不要 println!("{}", s.value()); s.increment(); println!("{}", s.value()); }
構造体のインスタンスをMutex(やArc<Mutex>)にする場合は、構造体自体はシングルスレッドで扱う場合と同じになる。(フィールドを変更するメソッドは&mut selfにする必要がある)
(むしろ、通常の構造体をマルチスレッドで扱う(スレッドセーフにする)為にArc<Mutex>にすると言うべきか)
use std::sync::{Mutex};
struct MyStruct { value: i32, } impl MyStruct { fn new(value: i32) -> MyStruct { MyStruct { value } } fn value(&self) -> i32 { self.value } fn increment(&mut self) { self.value += 1; } }
fn main() { let s = Mutex::new(MyStruct::new(1)); let mut s = s.lock().unwrap(); // &mut selfなメソッドを呼ぶので、ロックされた変数はmutにする必要がある println!("{}", s.value()); s.increment(); // &mut selfなメソッド println!("{}", s.value()); }
どういった時に参照(&)を使うのが良いか?[2025-07-06]
構造体(やタプル形式の列挙型)のフィールドでは、基本的に参照(&)を保持しない方が良い。
フィールドを参照にしようとすると、ライフタイムの問題が出てきてややこしくなる。
避けた方が無難。
(参照を保持するということは、その値の所有権は別のどこかが持ったままということになる。
所有者がいつ所有権を無くすか(値を破棄するか)は分からないので、そのために値の生存期間(ライフタイム)を考慮する必要が出てくる)
(したがって、構造体のフィールドで値を保持するなら、所有権ごと取った方が良い)
関数やメソッドを呼び出す場合、引数が参照として定義されていたら、参照で呼び出す(しかない)。
なので、関数呼び出しに関しては、特に考えることは無い。
なお、関数やメソッドを呼ぶ際に「所有権を渡す・渡さない」という言い方をするが、
自分は「関数が所有権を奪う」「関数に所有権を奪われる」と言った方が実態に合っているように思える。
所有権を奪うかどうか、すなわち引数が参照かどうかを決めるのは、関数やメソッド定義の側だから。
関数やメソッドを定義する場合、引数を参照にするかどうかを考える必要がある。
個人的な指針としては、引数は基本的に全部参照でいいと思う。
参照にしておかないと関数が所有権を奪ってしまうので、呼び出す側が所有権を持っておきたい(複数の呼び出しに使いたい)場合に、それが出来なくなってしまう。
一方で、以下のケースについては引数は参照でなくてもいいと思う。
ケース | 例 | 説明 |
---|---|---|
その関数を呼び出したら、それ以上使わせない場合 |
impl MyStruct { fn close(self) { 〜 } } |
closeされたら、それ以降インスタンスは使用させない。 (&が付かない)selfなら所有権を奪うので、呼び出し側に使用不可を強制できる。 |
構造体を作って引数の値をフィールドに保持したい場合 |
struct MyStruct { value: String, } impl MyStruct { fn new(value: String) -> MyStruct { MyStruct { value } } } |
構造体のフィールドは基本的に値を所有するので、所有権を貰う方がやりやすい。 |
RcやArcを受け取る場合 |
fn my_method(value: Rc<MyStruct>) { 〜 } |
RcやArcはクローンを作るコストが低いので、呼び出す側でクローンを作ることにも抵抗が無い。 |
構造体のフィールドの値を取得するゲッターメソッドは、値を参照(&)で返すのが良いと思う。
struct MyStruct { int_value: i32, str_value: String, opt_value: Option<String>, } impl MyStruct { fn new(int_value: i32, str_value: String, opt_value: Option<String>) -> MyStruct { MyStruct { int_value, str_value, opt_value, } } fn int_value(&self) -> i32 { // i32はCopy可なので、参照で返す必要は無い self.int_value } fn str_value(&self) -> &String { // StringはCopy不可なので、参照で返すのが良いだろう &self.str_value } fn opt_value(&self) -> Option<&String> { self.opt_value.as_ref() } }
Optionで保持しているフィールドのゲッターメソッドについては、&self.opt_value
だと&Option<String>を返すことになってしまう。
基本的にはそれでも問題ないが、何か不便なことがあった気がする…。
一般的にはOption<&String>形式で返すようだ。