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

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

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

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

Q&A

解決済

1回答

1150閲覧

Rustでライフタイムを解決したい。

I_am_

総合スコア23

Rust

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

0グッド

1クリップ

投稿2022/03/09 11:49

前提・実現したいこと

Rustのライフタイムで解決の仕方が分からないところがあります。
何か、知っていることや考え方を知っていたら教えてほしいです。

該当コード

以下のパターンの解決をしたいです。

rust

1// 2use anyhow::Result; 3use std::{collections::HashMap, vec}; 4use std::string::ToString; 5use std::ops::{Deref, DerefMut}; 6use strum::IntoEnumIterator; 7use strum_macros::{EnumIter, ToString}; 8use std::sync::Arc; 9use tokio::sync::RwLock; 10 11#[derive(Debug, Default, Clone)] 12struct Data { 13 id: String, 14 name: String, 15 value: i32, 16} 17 18#[derive(EnumIter, ToString, Debug, Hash, PartialEq, Eq, Clone)] 19enum Index { 20 PatternA, 21 PatternB, 22 PatternC, 23 PatternD, 24} 25 26#[derive(Debug, Default, Clone)] 27struct Helper { 28 up: Vec<Data>, 29 down: Vec<Data>, 30} 31impl Helper { 32 pub fn new() -> Self { 33 Self { 34 up: vec![], 35 down: vec![], 36 } 37 } 38} 39 40#[derive(Debug, Default, Clone)] 41pub struct Target { 42 store: HashMap<Index, Helper>, 43} 44 45impl Deref for Target { 46 type Target = HashMap<Index, Helper>; 47 48 fn deref(&self) -> &Self::Target { 49 &self.store 50 } 51} 52 53impl DerefMut for Target { 54 fn deref_mut(&mut self) -> &mut HashMap<Index, Helper> { 55 &mut self.store 56 } 57} 58 59impl Target { 60 pub fn new() -> Self { 61 Self { 62 store: HashMap::new(), 63 } 64 } 65 66 pub fn init(&mut self) -> Result<()> { 67 Index::iter().for_each(|i| { 68 self.store.insert( 69 i, 70 Helper::new(), 71 ); 72 }); 73 74 Ok(()) 75 } 76 77 pub async fn update<'a>(&'a mut self) -> Result<()> { 78 79 let multi_ref = Arc::new(RwLock::new(&self.store)); 80 let mut executor = vec![]; 81 82 for index in Index::iter() { 83 let key= format!("{}:id", index.to_string()); 84 85 let clone_ref = Arc::clone(&multi_ref); 86 let mut wt = clone_ref.write().await; 87 let mut data_store = wt.deref_mut().clone(); 88 89 if let Some(data_vec) = data_store.get_mut(&index) { 90 data_vec.up.sort_by(|a, b| b.value.cmp(&a.value)); 91 92 data_vec.down.sort_by(|a, b| a.value.cmp(&b.value)); 93 94 let up_task = insert_db(&data_vec.up, &key); 95 let down_taks = insert_db(&data_vec.down, &key); 96 97 executor.push(up_task); 98 executor.push(down_taks); 99 } 100 } 101 102 futures::future::join_all(executor).await; 103 Ok(()) 104 } 105} 106 107 108pub async fn insert_db(data: &[Data], key: &str) -> Result<()> { 109 110 Ok(()) 111}

具体的な説明。

  • 実現したいことは、store: HashMap<Index, HelperData>,に格納されているデータに対して非同期で処理を回したいです。

  • Index * 2つのvector (up sort, down sort)をjoin_allもしくはもっといい方法で非同期処理をしたいです。

  • 起きているエラーはjoin_allしている部分futures::future::join_all(executor).await;であり,

ここで全てのタスクをjoin_allしているので、for分のスコープ外までlife timeが生きていないかもしれないとのエラーだと思います。

例えば、以下のformat!のアロケートがスコープ外になります。。

rust

1let key= format!("{}:id", index.to_string()); 2↓こちらをこのようにしてもだめでした。 3let key: &'a str = &format!("{}", index.to_string());

また、その他にも問題があり。

rust

1if let Some(data_vec) = self.store.get_mut(&index) { 2 data_vec.up.sort_by(|a, b| b.value.cmp(&a.value)) 3 data_vec.down.sort_by(|a, b| a.value.cmp(&b.value)); 4/////省略。 5}
  • get_mut()で取得したSome(data_vec)が1度に複数の可変参照を取ることができないと問題が出ました。なので、こちらをArc::Mutexでlockすることで実現しようとしました。

  • コンパイラからdrefが欲しいと言われたのでその通りに実装して現在に至ります。そしてまた、ライフタイムの問題に戻ってきました。

error

1`data_store` does not live long enough 2values in a scope are dropped in the opposite order they are defined
  • 'aのライフタイム宣言をどのように実装すれば解決するか分からなくて詰まってます。

何か知っていましたら教えてほしいです。
また、他にももっとこのような実装の方がいいとかありましたら教えてください。

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

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

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

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

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

guest

回答1

0

ベストアンサー

こんにちは。
まず状況を確認したいのですが依存クレートは以下で合っているでしょうか。

toml

1[dependencies] 2anyhow = "1.0.56" 3futures = "0.3.21" 4strum = "0.24.0" 5strum_macros = "0.24.0" 6tokio = { version = "1.17.0", features = ["sync"]}

この前提の下回答します。

まず insert_db のライフタイムについてですが、 async を使わずに書くと以下と同等になります。

rust

1pub async fn insert_db<'a>(data: &'a[Data], key: &'a str) -> impl Future<Output = Result<()>> + 'a { 2 async{ Ok(()) } 3} 4

参考: async Lifetimes

返り型のライフタイムが 'a になっているこのに注目して下さい。このライフタイムが for の中までしか有効でないのでエラーになっています。これを解決してあげるとコンパイルが通ります。

案1

Futureのライフタイムが 'a なのが問題だったので 'static にしてあげると解決します。insert_dbasync fn 構文を使うのをやめて同等のコードにし、 Futureのライフタイムを 'static にするとコンパイルが通ると思います。

rust

1pub fn insert_db(data: &[Data], key: &str) -> impl Future<Output = Result<()>> + 'static { 2 async { Ok(()) } 3}

サンプルのコード上の変更点は最小ですが、 insert_db の実装を変更する必要がありますし、内部で使っているDBライブラリの設計によってはこのライフタイムを満たせない可能性もあります。

案2

そもそもFutureからライフタイムをなくせばライフタイムエラーはなくなります。つまり、 insert_db を以下のように定義し直します。

rust

1pub async fn insert_db(data: Vec<Data>, key: String) -> Result<()> { 2 Ok(()) 3}

そうすると ArcRwLock も使う必要もなくなり、コードがシンプルになります。

rust

1 pub async fn update<'a>(&'a mut self) -> Result<()> { 2 let mut executor = vec![]; 3 4 // self.storeをcloneする 5 for (index, mut data_vec) in self.store.clone().into_iter() { 6 let key = format!("{}:id", index.to_string()); 7 data_vec.up.sort_by(|a, b| b.value.cmp(&a.value)); 8 9 data_vec.down.sort_by(|a, b| a.value.cmp(&b.value)); 10 11 let up_task = insert_db(data_vec.up, key.clone()); 12 let down_taks = insert_db(data_vec.down, key); 13 14 executor.push(up_task); 15 executor.push(down_taks); 16 } 17 18 futures::future::join_all(executor).await; 19 Ok(()) 20 }

もし sort_by した結果を store に残しておきたいならアップデートする段とDBへinsertする段で for 式を2つに分けてあげるとよいでしょう。

rust

1 let mut executor = vec![]; 2 3 // values_mutで self.storeを可変借用する 4 for data_vec in self.store.values_mut() { 5 data_vec.up.sort_by(|a, b| b.value.cmp(&a.value)); 6 7 data_vec.down.sort_by(|a, b| a.value.cmp(&b.value)); 8 } 9 // self.storeをcloneする 10 for (index, data_vec) in self.store.clone().into_iter() { 11 let key = format!("{}:id", index.to_string()); 12 13 let up_task = insert_db(data_vec.up, key.clone()); 14 let down_taks = insert_db(data_vec.down, key); 15 16 executor.push(up_task); 17 executor.push(down_taks); 18 } 19 20 futures::future::join_all(executor).await; 21 Ok(()) 22 }

案3

for 式の内ではライフタイムの整合性がとれているので、 for 式の中をまるごとFutureにしてしまう手もあります。 async move 式を使えば for 式の中をまるごとFutureにしてしまえます。

rust

1 pub async fn update<'a>(&'a mut self) -> Result<()> { 2 let multi_ref = Arc::new(RwLock::new(&self.store)); 3 let mut executor = vec![]; 4 5 for index in Index::iter() { 6 // Arcのcloneだけasyncブロックの外でやらないといけない 7 let clone_ref = Arc::clone(&multi_ref); 8 9 // async moveでまるごと1つのFutureにする 10 let task = async move { 11 let key = format!("{}:id", index.to_string()); 12 let mut wt = clone_ref.write().await; 13 let mut data_store = wt.deref_mut().clone(); 14 if let Some(data_vec) = data_store.get_mut(&index) { 15 data_vec.up.sort_by(|a, b| b.value.cmp(&a.value)); 16 17 data_vec.down.sort_by(|a, b| a.value.cmp(&b.value)); 18 19 let up_task = insert_db(&data_vec.up, &key).await; 20 let down_taks = insert_db(&data_vec.down, &key).await; 21 } 22 }; 23 // タスクはここで登録する 24 executor.push(task) 25 } 26 27 futures::future::join_all(executor).await; 28 Ok(()) 29 }

参考: async move


以上修正案を3つ紹介しました。それぞれの特徴を述べておきます

  • 案1
    • 👍 ライフタイムが伸びて汎用的になる
    • 👎 insert_db の実装が難しい、あるいは無理な可能性がある
  • 案2
    • 👍 コードがシンプルになる。これ以上ライフタイムで困ることがなくなる
    • 👎 データのコピーが発生する
  • 案3
    • 👍 局所的な変更なので他のコードへの影響が発生しない
    • 👎 元のコードと違って up_taskdown_taks が並行に動かない

特徴を鑑みて自分の用途にあった案を採用してほしいのですが、私だったら案2→案1→案3の順に検討すると思います。

参考になれば。

投稿2022/03/10 02:12

blackenedgold

総合スコア468

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

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

I_am_

2022/03/10 12:28

回答ありがとうございます。 どれもビルド通りました。 結果、案3のメリットの他のコードへ影響しない方法を選びました。 それぞれの案と特徴とコードを見比べることができ勉強になりました。 感謝です。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問