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

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

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

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

Q&A

解決済

2回答

317閲覧

Rustのライフタイムエラーが解決できない:lifetime may not live long enough

tt-44

総合スコア2

Rust

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

0グッド

0クリップ

投稿2025/01/19 05:30

編集2025/01/20 15:19

実現したいこと

スタックを模倣したプログラムを作ります。
nestメソッドで新しいStackを返し、そのStackがドロップされるまでnest元のStack経由でメモリ確保はできません。これをmut借用の特性を使って表現したのが次のコードです。
解決にあたって変えてはいけないのは次のことです。

  • main関数内の処理を変えてはいけない。

できれば回避したいことは次です。

  • RefCellやRcは使いたくない

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

ライフタイム関連のエラーが出ます。

エラーメッセージ

error

1error: lifetime may not live long enough 2 --> src/main.rs:84:36 3 | 465 | impl<'a, 'b, T> Stack<'a, 'b, T> { 5 | -- -- lifetime `'b` defined here 6 | | 7 | lifetime `'a` defined here 8... 984 | parent: Parent::Parent(self), 10 | ^^^^ this usage requires that `'b` must outlive `'a` 11 | 12 = help: consider adding the following bound: `'b: 'a` 13 = note: requirement occurs because of a mutable reference to `Stack<'_, '_, T>` 14 = note: mutable references are invariant over their type parameter 15 = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

該当のソースコード

rust

1use std::collections::HashMap; 2 3fn main() { 4 let mut mem = Memory::new(); 5 let mut stack = Stack::new(&mut mem); 6 stack.alloc(0, "v1"); 7 { 8 let mut nest = stack.nest(); 9 nest.push(1, "v2"); 10 assert_eq!(nest.len(), 2); 11 } 12 assert_eq!(stack.len(), 1); 13 stack.alloc(2, "v3"); 14 assert_eq!(stack.len(), 2); 15} 16struct Memory<T> { 17 data: HashMap<u32, T>, 18} 19impl<T> Memory<T> { 20 fn new() -> Self { 21 Self { 22 data: HashMap::new(), 23 } 24 } 25 fn alloc(&mut self, id: u32, value: T) { 26 self.data.insert(id, value).unwrap(); 27 } 28 fn free(&mut self, id: u32) { 29 if self.data.remove(&id).is_some() { 30 panic!() 31 } 32 } 33} 34struct Stack<'a, 'b, T> { 35 ids: Vec<u32>, 36 parent: Parent<'a, 'b, T>, 37} 38enum Parent<'a, 'b, T> 39where 40 'a: 'b, 41{ 42 Root(&'a mut Memory<T>), 43 Parent(&'b mut Stack<'a, 'a, T>), 44} 45impl<'a, 'b, T> Parent<'a, 'b, T> { 46 fn alloc(&mut self, id: u32, value: T) { 47 match self { 48 Parent::Root(memory) => memory.alloc(id, value), 49 Parent::Parent(stack) => stack.alloc(id, value), 50 } 51 } 52 fn free(&mut self, id: u32) { 53 match self { 54 Parent::Root(memory) => memory.free(id), 55 Parent::Parent(stack) => stack.free(id), 56 } 57 } 58 fn len(&self) -> usize { 59 match self { 60 Parent::Root(memory) => memory.data.len(), 61 Parent::Parent(stack) => stack.len(), 62 } 63 } 64} 65impl<'a, 'b, T> Stack<'a, 'b, T> { 66 fn new(mem: &'a mut Memory<T>) -> Self { 67 Self { 68 ids: vec![], 69 parent: Parent::Root(mem), 70 } 71 } 72 fn push(&mut self, id: u32, value: T){ 73 self.ids.push(id); 74 self.alloc(id, value); 75 } 76 fn alloc(&mut self, id: u32, value: T) { 77 self.parent.alloc(id, value); 78 } 79 fn free(&mut self, id: u32) { 80 self.parent.free(id); 81 } 82 fn nest<'c>(&'c mut self) -> Stack<'a, 'c, T> 83 where 84 'b: 'c, 85 { 86 Stack { 87 ids: vec![], 88 parent: Parent::Parent(self), 89 } 90 } 91 fn len(&self) -> usize { 92 self.parent.len() 93 } 94} 95impl<'a, 'b, T> Drop for Stack<'a, 'b, T> { 96 fn drop(&mut self) { 97 for id in &self.ids { 98 self.parent.free(*id); 99 } 100 } 101} 102

試したこと・調べたこと

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

なお張り付けたコードのnest関数を現在の

rust

1fn nest<'c>(&'c mut self) -> Stack<'a, 'c, T> 2 where 3 'b: 'c,

ではなく、

rust

1fn nest(&'b mut self)->Stack<'a, 'b, T>

とすると、nestのエラーは消えますが、以下の(2)のエラーが代わりに出ます。

error[E0597]: stack does not live long enough
--> src/main.rs:8:24
|
5 | let mut stack = Stack::new(&mut mem);
| --------- binding stack declared here
...
8 | let mut nest = stack.nest();
| ^^^^^ borrowed value does not live long enough
...
15 | }
| -
| |
| stack dropped here while still borrowed
| borrow might be used here, when stack is dropped and runs the Drop code for type Stack

error[E0502]: cannot borrow stack as immutable because it is also borrowed as mutable
--> src/main.rs:12:16
|
8 | let mut nest = stack.nest();
| ----- mutable borrow occurs here
...
12 | assert_eq!(stack.len(), 1);
| ^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here

error[E0499]: cannot borrow stack as mutable more than once at a time
--> src/main.rs:13:5
|
8 | let mut nest = stack.nest();
| ----- first mutable borrow occurs here
...
13 | stack.alloc(2, "v3");
| ^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here

error[E0502]: cannot borrow stack as immutable because it is also borrowed as mutable
--> src/main.rs:14:16
|
8 | let mut nest = stack.nest();
| ----- mutable borrow occurs here
...
14 | assert_eq!(stack.len(), 1);
| ^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here

error: lifetime may not live long enough
--> src/main.rs:82:36
|
65 | impl<'a, 'b, T> Stack<'a, 'b, T> {
| -- -- lifetime 'b defined here
| |
| lifetime 'a defined here
...
82 | parent: Parent::Parent(self),
| ^^^^ this usage requires that 'b must outlive 'a
|
= help: consider adding the following bound: 'b: 'a
= note: requirement occurs because of a mutable reference to Stack<'_, '_, T>
= note: mutable references are invariant over their type parameter
= help: see https://doc.rust-lang.org/nomicon/subtyping.html for more information about variance

Some errors have detailed explanations: E0499, E0502, E0597.
For more information about an error, try rustc --explain E0499.
error: could not compile workspace (bin "workspace") due to 5 previous errors

補足

特になし

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

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

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

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

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

guest

回答2

0

ベストアンサー

parent を enum で実装していますが、trait を導入してジェネリクスにしてしまうのはどうでしょう。(下記ではPhantomData とかがでてきて面倒な形になってしまいましたが、もっといい実装があるかもしれません)

わたしもぼんやりとしか理解できていませんが、元のコードが動かない理由には variance とかがかかわっているように思います。https://blog.ymgyt.io/entry/lifetime-and-variance/
&'a mut TT に対して invariant なので、参照を含むものの &mut をとると、ライフタイムの関係が壊れてしまいそうです。
(Parent(&'b mut Stack<'a, 'a, T>)のところの'a)
そこを別のライフタイムにしてどうにかしようとすると、別回答のコメントにあるように任意回のネストをうまく表現することができないです。ジェネリクスにすれば、そのあたりをコンパイラがいい感じにしてくれているように思います。

rust

1use std::collections::HashMap; 2use std::marker::PhantomData; 3 4fn main() { 5 let mut mem: Memory<&'static str> = Memory::new(); 6 let mut stack = Stack::new(&mut mem); 7 stack.alloc(0, "v1"); 8 { 9 let mut nest = stack.nest(); 10 nest.push(1, "v2"); 11 { 12 let mut nest = nest.nest(); 13 nest.push(2, "v1"); 14 nest.push(3, "v2"); 15 assert_eq!(nest.len(), 4); 16 } 17 assert_eq!(nest.len(), 2); 18 } 19 stack.alloc(2, "v3"); 20 assert_eq!(stack.len(), 2); 21} 22 23struct Memory<T> { 24 data: HashMap<u32, T>, 25} 26 27impl<T> Memory<T> { 28 fn new() -> Self { 29 Self { 30 data: HashMap::new(), 31 } 32 } 33} 34 35trait Parent<T> { 36 fn alloc(&mut self, id: u32, value: T); 37 fn free(&mut self, id: u32); 38 fn len(&self) -> usize; 39} 40 41impl<T> Parent<T> for Memory<T> { 42 fn alloc(&mut self, id: u32, value: T) { 43 if self.data.insert(id, value).is_some() { 44 panic!() 45 } 46 } 47 48 fn free(&mut self, id: u32) { 49 if self.data.remove(&id).is_none() { 50 panic!() 51 } 52 } 53 54 fn len(&self) -> usize { 55 self.data.len() 56 } 57} 58 59struct Stack<'a, T, U: Parent<T>> { 60 ids: Vec<u32>, 61 parent: &'a mut U, 62 _p: PhantomData<T>, 63} 64 65impl<'a, T> Stack<'a, T, Memory<T>> { 66 fn new(mem: &'a mut Memory<T>) -> Self { 67 Self { 68 ids: vec![], 69 parent: mem, 70 _p: PhantomData, 71 } 72 } 73} 74 75impl<'a, T, U: Parent<T>> Stack<'a, T, U> { 76 fn nest<'b>(&'b mut self) -> Stack<'b, T, Self> { 77 Stack { 78 ids: vec![], 79 parent: self, 80 _p: PhantomData, 81 } 82 } 83 84 fn push(&mut self, id: u32, value: T) { 85 self.ids.push(id); 86 self.alloc(id, value); 87 } 88} 89 90impl<T, U: Parent<T>> Parent<T> for Stack<'_, T, U> { 91 fn alloc(&mut self, id: u32, value: T) { 92 self.parent.alloc(id, value); 93 } 94 95 fn free(&mut self, id: u32) { 96 self.parent.free(id); 97 } 98 99 fn len(&self) -> usize { 100 self.parent.len() 101 } 102} 103 104impl<T, U: Parent<T>> Drop for Stack<'_, T, U> { 105 fn drop(&mut self) { 106 for id in &self.ids { 107 self.parent.free(*id); 108 } 109 } 110}

投稿2025/01/24 01:56

編集2025/01/24 07:41
bsdfan

総合スコア4843

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

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

tt-44

2025/01/26 17:33

解決策とエラーの要因も示した丁寧な回答ありがとうございます。 ベストアンサーに選ばせていただきたいと思います
guest

0

エラーメッセージにあるとおり 'b: 'a をつけてあげるとエラーは解決します。

rust

1 fn nest<'c>(&'c mut self) -> Stack<'a, 'c, T> 2 where 3 'b: 'a, 4 'b: 'c, 5 { 6 Stack { 7 ids: vec![], 8 parent: Parent::Parent(self), 9 } 10 }

ところで元のコードを走らせると2箇所パニックになります。
どちらも HashMap への操作で、操作前に値が入っていた場合と入っていなかった場合を表わす Option が返っているのに対して期待と実際が異なっているのが原因です。

rust

1impl<T> Memory<T> { 2 // ... 3 fn alloc(&mut self, id: u32, value: T) { 4 // idに対応する値が既に入っていた場合に `Some` が返る。 5 // 入っていないのが期待する動作と思われる 6 if self.data.insert(id, value).is_some() { 7 panic!() 8 } 9 } 10 fn free(&mut self, id: u32) { 11 // idに対応する値が既に入っていた場合に `Some` が返る 12 // 入っているのが期待する動作と思われる。 13 if self.data.remove(&id).is_none() { 14 panic!() 15 } 16 } 17}

2関数を上記のコードに差し替えたらプログラムがエラーなく動作することを確認できました。

投稿2025/01/21 07:07

blackenedgold

総合スコア472

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

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

tt-44

2025/01/23 15:22

丁寧な回答ありがとうございます。確かにエラーが消えました。 ただ、確認後少しコードを変えてみて、コードにおける意図を鑑みた同様のコードとして、ネストをさらに深くした次のコードはいまだエラーが出ます。 ```rust fn main(){ let mut mem: Memory<&'static str> = Memory::new(); let mut stack = Stack::new(&mut mem); stack.alloc(0, "v1"); { let mut nest = stack.nest(); nest.push(1, "v2"); { let mut nest = nest.nest(); nest.push(2, "v1"); nest.push(3, "v2"); } } stack.alloc(2, "v3"); } ``` ``` のようにしたところ、エラーが出ます。もしくは ```rust fn se<'a, 'b>(mut stack: Stack<'a, 'b, &'static str>) { stack.push(2, "v1"); stack.push(3, "v2"); } fn main(){ let mut mem: Memory<&'static str> = Memory::new(); let mut stack = Stack::new(&mut mem); stack.alloc(0, "v1"); { let mut nest = stack.nest(); nest.push(1, "v2"); se(nest.nest()); } stack.alloc(2, "v3"); } ``` これは回避可能でしょうか。
blackenedgold

2025/01/24 00:11

それは難しそうです。パラメータ2つ使って2つ分のライフタイムを表現していたところを任意回のネストにすると表現できなくなるみたいです
tt-44

2025/01/26 17:34

了解です。今回はベストアンサーは別の方になりましたが、非常に参考になりました。改めて回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.33%

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

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

質問する

関連した質問