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

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

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

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

Q&A

解決済

1回答

611閲覧

参照から元の変数の所有権を得たい

qope

総合スコア16

Rust

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

1グッド

0クリップ

投稿2022/07/03 05:13

編集2022/07/03 05:15

ある変数xの参照y = &xを用いて、(元のxを破棄しながら)xの所有権を得るにはどうすれば良いでしょうか?
例えば、以下のコードにおいて、変数fruitfruitsが所有しているString型への参照となっていますが、これを関数の返り値としてmoveしようとすると、エラーが起こります。これはfruits"banana"の所有権を持っているためだと思うのですが、fruits"banana"の所有権を取得して返り値として返すにはどうすれば良いでしょうか?

この例ではfor fruit in fruitsとして、初めからfruitに所有権をmoveしてやれば良いですが、現在書いているコードではreturn直前まで所有権をmoveしたくないので、困っています。

rust

1fn main() { 2 println!("{:?}", get_banana()); 3} 4 5fn get_banana()->String{ 6 let fruits: Vec<String> = vec!["apple".to_string(), "banana".to_string(), "watermelon".to_string()]; 7 for fruit in &fruits{ //ここは変更したくない 8 if fruit == &"banana".to_string(){ 9 return *fruit; // error[E0507]: cannot move out of `*fruit` which is behind a shared reference 10 } 11 } 12 return "no banana".to_string(); 13}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5e56d6b5c715969dfb93351dc898c2ba

equal-l2👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

ある変数xの参照y = &xを用いて、(元のxを破棄しながら)xの所有権を得るにはどうすれば良いでしょうか?

safeなRustではできません。参照&は参照先のデータ(この例ではVec<String>)を変更しないという約束の下にデータを借用するためのものですので、safeなRustではその約束を破れないのです。

safeなRustでの一般的な対処法として思いつくのは以下のようなものです。

  • 方法1:clone&StringからStringを作る
    • 巨大な文字列でない限り、性能は劣化しません
  • 方法2:Vec<String>上での"banana"の位置を調べて、見つかったらVecswap_removeメソッドで該当の要素を取り出す
    • swap_remove&mut fruitを要求します
  • 方法3:もし&fruitsのイテレーターの代わりに&mut fruitsのイテレーターを使えるなら
    • mem::takeメソッドでString::default(新しい空の文字列)と交換する

方法1

rust

1 for fruit in &fruits { 2 if fruit == "banana" { // 質問とは関係ないが、&Stringは&strと比較可能 3 // cloneで&StringからStringを作る 4 return fruit.clone(); 5 } 6 }

方法2

rust

1 // mutに変える 2 let mut fruits: Vec<String> = ... 3 4 // "banana"の位置(インデックス)を調べる 5 let mut banana_idx = None; 6 for (i, fruit) in fruits.iter().enumerate() { 7 if fruit == "banana" { 8 banana_idx = Some(i); 9 break; 10 } 11 } 12 13 // 見つかったら、その位置の要素を取り出して返す 14 if let Some(i) = banana_idx { 15 // &mut fruitが要求される 16 return fruits.swap_remove(i); 17 }

なおforのところは以下のようにイテレーターのpositionメソッドで書き換えられます。

rust

1 let banana_idx = fruits.iter().position(|fruit| fruit == "banana");

方法3

&fruitsのイテレーターの代わりに&mut fruitsのイテレーターを使える場合に可能になる方法です。

rust

1 // mutに変える 2 let mut fruits: Vec<String> = ... 3 4 // &mutに変える 5 for fruit in &mut fruits { 6 if fruit == "banana" { 7 // &mut Stringなら、mem::takeで取り出すことができる。 8 // Vec<String>の元の場所には`String::default()` 9 //(新しい空の文字列)が入る 10 return std::mem::take(fruit); 11 } 12 }

まとめ

新たにStringを作らないためには方法2を取る必要がありますが、コードは長くなってしまいます。

よほど大きなStringでない限りは、性能面で違いの出ない方法1が簡潔で良いと思います。

方法3も方法1のように新たにStringを作りますが、cloneと違って空の文字列なので、heapメモリーのアロケーションが起きないという、わずかな違いがあります。

投稿2022/07/03 23:52

編集2022/07/03 23:58
tatsuya6502

総合スコア2035

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

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

qope

2022/07/04 02:26

なるほど、ストイックにやるなら方法2で、ある程度妥協するなら方法1になりそうですね。 現在書いているコードでは、データ量は小さいですが同様の処理を非常に多く繰り返す必要があるので両者でパフォーマンスを比較してみようと思います。方法3はfor文内でfruitsの参照を利用する必要があるので利用できませんが、`std::mem::take`は知らなかったので勉強になりました。 丁寧なご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問