質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

363閲覧

Rust の lifetime に関しての質問

KN2018

総合スコア22

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2025/04/01 02:38

実現したいこと

以下のコードで、f(&mut s) を実行しようとするとエラーが発生してコンパイルできません。私が lifetime の仕組みを正確に理解できていないためですが、f のような定義をすると、どうしてエラーになるのかを教えてくださると、大変助かります。

発生している問題・分からないこと

コメントアウトされている f(&mut s) を実行しようとするとコンパイルできません。

エラーメッセージ

error

1 | f(&mut s); 2 | ------ first mutable borrow occurs here 3 | g1(&mut s); 4 | ^^^^^^ 5 | | 6 | second mutable borrow occurs here 7 | first borrow later used here 8

該当のソースコード

Rust

1struct S<'a> { 2 ref_val: &'a mut i32, 3} 4 5impl<'a> S<'a> { 6 fn new(ref_val: &'a mut i32) -> Self { 7 S { 8 ref_val, 9 } 10 } 11} 12 13fn f<'a>(s: &'a mut S<'a>) { 14 *s.ref_val += 1; 15} 16 17fn g1<'a>(s: &'a mut S) { 18 *s.ref_val += 1; 19} 20 21fn g2<'a>(s: &mut S<'a>) { 22 *s.ref_val += 1; 23} 24 25fn g3(s: &mut S) { 26 *s.ref_val += 1; 27} 28 29fn test_1() { 30 let mut val: i32 = 0; 31 let mut s = S::new(&mut val); 32 33// f(&mut s); 34 g1(&mut s); 35 g2(&mut s); 36 g3(&mut s); 37} 38

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

AI にも質問しましたが、g1、g2、g3 で可変参照を複数回利用しているため、エラーになるというような返答で、なかなか良い解答を得ることができていません。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KN2018

2025/04/01 06:04

いろいろ考察をしてくださって、ありがとうございます。 教えてくださったコードを見てみてましたが、掲載されているものは、当然にコンパイルが通っていはいけないコードです。Vec の中の要素への参照を保持しようとしていますが、Vec は可変ですので、工夫もなく Vec 内への参照を保持してはいけません。ダングリングポインタが生まれる可能性のあることをしています。該当記事を書いた方は気付いていないようですが、論理エラーがあるプログラムですので、コンパイラがエラーを出すのも当然だと思われます。
KN2018

2025/04/01 06:30

言葉足らずですみません。Vec が可変というのは、いわゆる可変参照などではなくて、メモリ内での移動を含めてです。いわゆる自己参照をメンバ変数に持つときには注意が必要、ということを考えていました。
bsdfan

2025/04/01 08:01 編集

(質問の意味を勘違いしていたので削除します)
guest

回答2

0

ベストアンサー

fn f<'a>(s: &'a mut S<'a>) とすると、本来別々であるはずの

  • S の中の ref_val: &mut i32 のライフタイム(つまりは &mut val のライフタイム)と
  • 引数 &mut s のライフタイム

が同じという宣言なので、本来なら関数が終わったら不要になる &mut s の参照が解放されず、次の関数で可変参照が取れずエラーになるのではないでしょうか。

次のように異なるライフタイムを割り当ててやればエラーは出ないです。

rust

1fn f<'a, 'b>(s: &'b mut S<'a>) where 'a: 'b { 2 *s.ref_val += 1; 3}

追記

このあたり変性(Variance)に関わってくると思います。
うまく説明できるほど理解できていないのでリンクを紹介します。(本質問と同じことが説明されています)
https://qiita.com/maueki/items/b5df36e92561450938dd

&'a mut TTに対してinvariant(不変)なので、&'a mut Foo<'a> では前側(&'a ...)のライフタイムが、後側(Foo<'a>) のライフタイムに引っ張られるようなイメージなのかなと思います。
リンク先に書かれていますが、アンチパターンなんだと思います。

投稿2025/04/01 08:16

編集2025/04/01 23:39
bsdfan

総合スコア4899

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KN2018

2025/04/01 14:18

実際にコードを書いて試してみてくださって、大変ありがとうございます! まったく指摘してくださっている通りで、本来は別々のライフタイムを持つもので、実は、投稿したコードには含まれていないのですが、`g4<'a, 'b>(s: &'b mut S<'a>)` というシグネチャも試していて、where 句がなくても、コンパイラがライフタイムの長短を自動的に判定してくれて、エラーが出ないことは確認しています。 ただ、興味本位でちょっと条件を狭くしたつもりの `f<'a>(s: &'a mut S<'a>)` が、なぜコンパイルできないのかな?と悩んでいます。 bsdfan さんがおっしゃってる通り、今はライフタイムの問題ではなく、参照が解放されず、所有権でのエラーになってるのかな、と考えています。(エラーメッセージも所有権について言っていますし、、、) ただ、なぜ参照が解放されないのでしょうか。関数 f が実行を終えた時点で、参照は解放されるように思うのですが、、、。 やはり、`S<'a>` への参照のライフタイムが `'a` となる、という設定がおかしいのでしょうか。 すみません、もう少し何かヒントがあれば教えてくださると、とてもありがたいです。
bsdfan

2025/04/02 01:00 編集

本文追記しました。 > bsdfan さんがおっしゃってる通り、今はライフタイムの問題ではなく、参照が解放されず、所有権でのエラーになってるのかな、と考えています。(エラーメッセージも所有権について言っていますし、、、) 所有権でのエラーではなく、ライフタイムの問題です。 コンパイラが、f()の引数にマッチするようにライフタイムを推測した結果、仮引数の参照のライフタイム(というより変数スコープなのかも?)が関数呼び出し時点で終わらないように引き延ばされているということだと思います。
KN2018

2025/04/02 05:34 編集

解説を追記して下さって、とても感謝しています! こういう思考実験を一人でやっていると行き詰ることが多いのですが、助言をいただけることによって、思考が進んで大変助かります。 まだ、完全な理解ではないかもしれませんが、かなり納得できるようになりました。Rust の lifetime はスコープを表すこと、また、借用チェックはスコープによって行われること、この2点を考えることによって納得できるようになりました。 bsdfan がおっしゃっていることは、以下のようなことで正しいでしょうか? 自分としては、スコープは以下のようなものを想定。 ``` 'x: { let mut val = 0; let mut s = S<'x>::new(&mut val); <- S.ref_val の生存範囲は 'x 'y1: { f(&mut s); <- &mut s の生存範囲は 'y1(と間違えて考えている) } 'y2: { g1(&mut s); <- &mut s の生存範囲は 'y2 } ... } ``` しかし、私が f のシグネチャを f<'a>(s: &'a mut S<'a>) としたことによって、'y1 のスコープが 'x にまで拡大されて、その結果、スコープ 'y2 において借用チェックに引っかかるようになるんじゃないかと。 助言をいただけることによって、かなり納得できるようになりました。大変ありがとうございます! もし、考え方に間違いがあれば、お忙しいところ申し訳ないのですが、助言をくださると大変嬉しいです。 大変ありがとうございました。
bsdfan

2025/04/02 05:34

はい。私の理解も同じです。うまく説明できませんでしたが、理解いただけたようでよかったです。
guest

0

ふと「仮引数に所有権が束縛されているのが原因じゃないか」と思い立ち少し修正してみました

警告付きですが通るみたいです

struct S<'a> { ref_val: &'a mut i32, } impl<'a> S<'a> { fn new(ref_val: &'a mut i32) -> Self { S { ref_val, } } } //戻り値を追加 fn f<'a>(s: &'a mut S<'a>) ->&mut S<'a>{ *s.ref_val += 1; s } fn g1<'a>(s: &'a mut S) { *s.ref_val += 1; } fn g2<'a>(s: &mut S<'a>) { *s.ref_val += 1; } fn g3(s: &mut S) { *s.ref_val += 1; } fn test_1() { let mut val: i32 = 0; let mut s = S::new(&mut val); let mut ss=&mut f(&mut s); //ポインタの所有権を移動 g1(ss); g2(ss); g3(ss); println!("Ok!"); } fn main(){ test_1(); }
warning: variable does not need to be mutable --> compiler.rs:34:6 | 34 | let mut ss=&mut f(&mut s); | ----^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default warning: 1 warning emitted Ok!

投稿2025/04/01 05:17

Manabu

総合スコア103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KN2018

2025/04/01 06:20

コンパイルして検討して下さって、大変ありがとうございます! 実は、自分ながらにこういうことではないか?と思い当たることはあるのですが、確証がなく困っているところです。 その前に、せっかくコードを書いてくださったので、まずは、その警告が出ないようにしてみました。 f の戻り値を少し変更して `->&'a mut S<'a>`とします。そして、f の戻り値をそのまま受け取ってください。 `let ss = f(&mut s);` 上記のようにすれば、警告が出なくなるので良いと思います。 私が今思っているのは、コンパイラは、スコープ内での最小の lifetime を採用すると「思う」(私が勝手に思っているだけです、、、)ので、`f(&mut s)` を実行した時点で、s が持つ lifetime が、`&mut s` にまで短縮されて、それで、f の実行を終えて次の実行に移る前に、`&mut s` の消滅とともに、s の lifetime も消滅するのかな?と。そんなことあるのかなー?と思っているところです。 ですので、戻り値として、渡した `&mut s` をもう一度受け取って、その lifetime が残りスコープに延長されれば、s も消滅せずに生き残るのかな?と。 どんなものでしょうか??、、、
Manabu

2025/04/01 15:26

こちらを読む限り、ライフタイムを縮小する機能は無さそうです https://doc.rust-jp.rs/rust-nomicon-ja/lifetimes.html これは想像ですが、関数宣言によって新たに作られたライフタイムを取得しているのではないかと思います &mutで実引数を受け取る場合、専用のスコープが生成されるとあるので、このスコープをライフタイムとして明示的に仮引数へ渡す時に、引数がこのライフタイムに制限されるものと思われます 関数を抜ける場合、このスコープ内で&mutも消化されるため、これを受け取る変数が必要と考えられます またフィールドのライフタイムはインスタンスのそれに縛られるので、&mut valが広域のスコープに属していることを踏まえると、関数が受け取る局所的なスコープは、この広域スコープを包括しなければなりません 参照を代入する場合、その変数のライフタイムは参照元よりも狭くなります つまりは普段はライフタイムを推論するものの、それが明示的に指定された場合にライフタイムが一致するシンボルが必要になるものと考えられます
KN2018

2025/04/02 05:30

追記の助言をくださって、大変ありがとうございます! Manabu さんが助言してくださっていることも、bsdfan さんと同じことを指摘して下さっているのだと思います。 Manabu さんの助言も大変ありがたく、ベストアンサーとしたいのですが、いままで Variance について調べようと思いつつ(他言語でも必要な概念ですし、、、)調べてこなかったので、その点を考える良い機会となったと思ったので、bsdfan さんの方を僭越ながらベストアンサーとさせていただきました。 時間をかけて回答を書いてくださって、大変ありがとうございます。 すみませんが、また何かの機会がありましたら、声を掛けてくださると大変嬉しいです。
Manabu

2025/04/02 11:45

こちらもRustのライフタイムについて改めて見直すいい機会になりました 参考になったようで幸いです
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問