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

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

新規登録して質問してみよう
ただいま回答率
85.37%
参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

クロージャ

クロージャは、プログラミング言語における関数オブジェクトの一種です。 引数以外の変数を実行時の環境ではなく、 自身が定義された環境において解決することを特徴とします。

Rust

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

Q&A

解決済

1回答

1871閲覧

【Rust】クロージャ(ラムダ)からメソッドを呼び出す処理は、Rubyなら簡単なのにRustではコンパイルエラーになってしまう

akira_kano1101

総合スコア25

参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

クロージャ

クロージャは、プログラミング言語における関数オブジェクトの一種です。 引数以外の変数を実行時の環境ではなく、 自身が定義された環境において解決することを特徴とします。

Rust

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

0グッド

1クリップ

投稿2022/03/29 11:08

編集2022/03/29 22:47

Rubyなら簡単に動かせるコードがRustでは通らない

(エラーメッセージの読み方が間違っていたので修正しました。)

こんにちは。Rustでソフトウェアの開発を行なっています。

Rubyならラムダの中でさらに同インスタンスメソッドが呼び出し# (1)および代入# (2)ができます。

ruby

1# Rubyならこう書けば実現できるが… 2class Card 3 def run(assignor) 4 assignor[28] 5 end 6end 7 8class App 9 def assign(value) 10 @value = value # (2) 11 end 12 def setup 13 @value = 42 14 @card = Card.new 15 end 16 def update 17 @card.run( 18 ->(value) { self.assign(value) } # (1) 19 ) 20 puts @value # => 28と表示される 21 end 22end 23 24def main 25 app = App.new 26 app.setup 27 app.update 28end 29 30main

同様の動作をRustでも実現したい、しかし…

クロージャで同じように同オブジェクトのメソッドを呼び出そうと考えました。

しかしこれはうまくいきません。

rust

1// Rustではコンパイルエラーになる 2#[derive(Default)] 3pub struct Card; 4impl Card { 5 pub fn new() -> Self { 6 Self 7 } 8 pub fn run<F>(&self, mut assignor: F) 9 where 10 F: FnMut(i32), 11 { 12 assignor(28); 13 } 14} 15 16#[derive(Default)] 17struct App { 18 value: i32, 19 card: Card, 20} 21impl App { 22 fn assign(&mut self, value: i32) { 23 self.value = value; 24 } 25 pub fn setup(&mut self) { 26 self.value = 42; 27 self.card = Card::new(); 28 } 29 pub fn update(&mut self) { 30 self.card.run(|value| { 31 self.assign(value); 32 }); 33 println!("{}", self.value); 34 } 35} 36 37fn main() { 38 let mut app = App::default(); 39 app.setup(); 40 app.update(); 41}

エラーメッセージ抜粋

error[E0500]: closure requires unique access to `*self` but it is already borrowed --> src/main.rs:29:23 | 29 | self.card.run(|value| { | - --- ^^^^^^^ closure construction occurs here | | | | _________| first borrow later used by call | | 30 | | self.assign(value); | | ---- second borrow occurs due to use of `*self` in closure 31 | | }); | |__________- borrow occurs here

Rustでは二重に借用できないルールがあるようで、コンパイルできませんでした。

Rubyの書き方が非常に便利なため可能であればこれを何とかして実現したいです。

もし解決方法をご存じの方いらっしゃれば、恐縮ですが教えていただけないでしょうか。

よろしくお願いいたします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

Rustでは二重に借用できないルールがあるようで、コンパイルできませんでした。

Rustではメモリー安全性を保証するために、可変の借用は他の借用(可変・不変を問わず)と同時に存在できません。一方、不変の参照は、他の不変の借用と同時に存在できます。

エラーメッセージが示しているとおり、self.cardの不変借用を使用中に、assignメソッドのためにself 全体 の可変借用を作ることはできません。なぜならself全体の可変借用にはself.cardの可変借用が含まれるからです。

self.cardの借用とself.valueの借用なら、お互いを含んでいませんので同時に存在できます。assignメソッドを使わず、以下のようにすればコンパイルエラーが解消します。

rust

1 pub fn update(&mut self) { 2 // cardとvalueを、それぞれ借用する 3 let (card, v) = (&self.card, &mut self.value); 4 5 // 両方の借用を同時に使用する 6 card.run(|value| { 7 *v = value 8 }); 9 10 println!("{}", self.value); 11 }

追記

assignメソッドをなぜ使いたいかというと、本当はassignメソッド内でしている処理がもう少し複雑で、さらに今回のようなクロージャ内での処理が何度か似たような形で存在し、assignメソッドにまとめることでDRYにしたかったからです。
...
他に代替案などあれば幸いです。

CellRefCellなどの、内部可変性(interior mutability)を実現するコンテナーを使う方法があります。

valueの型をi32からstd::cell::Cell<i32>型へ変えることで、valueの不変借用を通じてCell内の値を変更できるようになります。selfの可変借用が不要になりますので、assignメソッドの引数を(&mut self, value: i32)から(&self, value: i32)へ変更できます。不変借用は同時に複数存在できますので、コンパイルエラーを解消できます。

rust

1use std::cell::Cell; 2 3#[derive(Default)] 4struct App { 5 // Cell<i32>に変更 6 value: Cell<i32>, 7 card: Card, 8} 9 10impl App { 11 // &mut self → &selfに変更 12 fn assign(&self, value: i32) { 13 // Cellのsetメソッドを使用する 14 self.value.set(value); 15 } 16 17 pub fn setup(&mut self) { 18 self.value.set(42); 19 self.card = Card::new(); 20 } 21 22 pub fn update(&mut self) { 23 self.card.run(|value| { 24 self.assign(value); 25 }); 26 27 // Cellのgetメソッドを使用する 28 println!("{}", self.value.get()); 29 } 30}

Cellgetのたびに値をコピーします。そのためgetメソッドはi32のようなCopyトレイトを実装した型でのみ利用可能です。なお、Copyトレイトを実装しない型についてはRefCellが使用できます。どちらのコンテナーも、実行時に性能上のオーバーヘッドが多少あります。

内部可変性について、詳しくは こちら を参照してください。

投稿2022/03/30 14:16

編集2022/03/31 00:54
tatsuya6502

総合スコア2046

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

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

akira_kano1101

2022/03/30 21:47

tatsuya6502様 回答ありがとうございます。 申し訳ありません、私の記載不足でした。 おっしゃる通り、クロージャ内でメソッドを使わなければ代入することが可能だということは私も試行錯誤して気づいていました。 `assign`メソッドをなぜ使いたいかというと、本当は`assign`メソッド内でしている処理がもう少し複雑で、さらに今回のようなクロージャ内での処理が何度か似たような形で存在し、`assign`メソッドにまとめることでDRYにしたかったからです。 あえてメソッドを用いて代入を担わせていたのはそういう理由です。 他に代替案などあれば幸いです。
tatsuya6502

2022/03/30 23:10

そうだったんですね。Cellを使った方法を解答に追記しました。
akira_kano1101

2022/03/31 03:00

こういう答えを求めていました。 非常に助かりました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問