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

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

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

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

クロージャ

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

Rust

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

Q&A

解決済

1回答

1177閲覧

【Rust】クロージャ(ラムダ)に絡むコードがRubyなら簡単に動くがRustではコンパイルエラーになるのを解決したい。

akira_kano1101

総合スコア25

Ruby

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

クロージャ

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

Rust

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

0グッド

1クリップ

投稿2022/04/03 11:48

編集2022/04/04 10:37

Rubyなら簡単に動かせるコードをRustでも実現したい

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

問題が二つあるので簡単に説明します。まずは記事中「問題のRustのコード」を確認していただきたいのですが、

  • (*self.renderer)()でクロージャ実行をしたいが共有参照&mutが関与しているのが原因なのかFのムーブが関与しているのが原因なのか、型サイズが不明なのが原因なのか、クロージャが呼べません。
  • ライフタイムの関係でクロージャ内の一時変数がドロップされるのが理由なのか、構造体にそのクロージャを持たせたいのですが上手くできません。(こちらの方が特に問題)

これらを解決したいです。

検証のため問題を再現した最小のRustのコードを以下に用意しました。

もしご存じの方いらっしゃれば教えていただけないでしょうか。

(Rubyでは実行できることを確認したので追記しました。)

本題

動作するRubyのコード

Rubyでなら同じ問題は起こりません。

ruby

1class Card 2 def initialize(renderer) 3 @renderer = renderer 4 end 5 def render 6 @renderer[] 7 end 8end 9 10class Presenter 11 def get 12 "Message" 13 end 14end 15 16class App 17 def initialize 18 @presenter = Presenter.new 19 @card = Card.new(->() { "" }) 20 end 21 def setup 22 @presenter = Presenter.new 23 @card = Card.new(->() { @presenter.get() }) 24 end 25 def update 26 puts @card.render # => "Message"と表示される 27 end 28end 29 30def main 31 app = App.new 32 app.setup 33 app.update 34end 35 36main

問題のRustのコード(検証用)

そしてこちらが問題です。

rust

1struct Presenter; 2 3impl Presenter { 4 pub fn get(&self) -> String { 5 "Message".to_string() 6 } 7} 8 9struct Card<'a, F> 10where 11 F: ?Sized + FnOnce() -> String, 12{ 13 renderer: &'a F, 14} 15 16impl<'a, F> Card<'a, F> 17where 18 F: ?Sized + FnOnce() -> String, 19{ 20 pub fn new(renderer: &'a F) -> Self { 21 Self { renderer: renderer } 22 } 23 pub fn render(&self) -> String { 24 (*self.renderer)() // コンパイルエラー 25 } 26} 27 28struct App<'a> { 29 presenter: Presenter, 30 card: Card<'a, dyn FnOnce() -> String>, 31} 32 33impl<'a> App<'a> { 34 pub fn new() -> Self { 35 Self { 36 presenter: Presenter, 37 card: Card::new(&|| "".to_string()), 38 } 39 } 40 pub fn setup(&mut self) { 41 self.card = Card::new(&|| self.presenter.get()); // コンパイルエラー 42 } 43 pub fn update(&mut self) { 44 println!("{:?}", self.card.render()); 45 } 46} 47 48fn main() { 49 let mut app = App::new(); 50 app.setup(); 51 app.update(); 52}

エラーメッセージ抜粋

error[E0161]: cannot move a value of type F: the size of F cannot be statically determined --> src/main.rs:24:9 | 24 | (*self.renderer)() | ^^^^^^^^^^^^^^^^ error[E0507]: cannot move out of `*self.renderer` which is behind a shared reference --> src/main.rs:24:9 | 24 | (*self.renderer)() | ^^^^^^^^^^^^^^^^ move occurs because `*self.renderer` has type `F`, which does not implement the `Copy` trait error: lifetime may not live long enough --> src/main.rs:41:31 | 40 | pub fn setup(&mut self) { | - let's call the lifetime of this reference `'1` 41 | self.card = Card::new(&|| self.presenter.get()); | ^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static` error[E0716]: temporary value dropped while borrowed --> src/main.rs:41:32 | 33 | impl<'a> App<'a> { | -- lifetime `'a` defined here ... 41 | self.card = Card::new(&|| self.presenter.get()); | -----------------------^^^^^^^^^^^^^^^^^^^^^^^- | | | | | creates a temporary which is freed while still in use | assignment requires that borrow lasts for `'a` 42 | } | - temporary value is freed at the end of this statement

ご回答のほどよろしくお願いいたします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

コンパイルエラーの解消法はひとつではなく、どれも一長一短になりそうです。CardrendererFnOnceという一回だけ呼べるクロージャーになっているので、この回答ではFnOnceは保ったまま、他を変えることでコンパイルエラーを解消してみました。

以下のように修正すると、コンパイルエラーが解消します。

rust

1use std::rc::Rc; 2 3struct Presenter; 4 5impl Presenter { 6 pub fn get(&self) -> String { 7 "Message".to_string() 8 } 9} 10 11struct Card<F> { 12 // クロージャーを借用するのではなく、所有する 13 // Rustの値(クロージャーなど)は所有者がいないと存在できないため、誰かが所有者に 14 // なる必要がある 15 // また、FnOnceは1回しかコールできないので、Option型にして、コール時にNoneにする 16 renderer: Option<F>, 17} 18 19impl<F> Card<F> 20where 21 F: FnOnce() -> String, 22{ 23 pub fn new(renderer: Option<F>) -> Self { 24 Self { renderer } 25 } 26 27 pub fn render(&mut self) -> String { 28 // Option型のtakeメソッドは、参照先の値をNoneと交換することで、 29 // 値をムーブアウトさせる。(self.rendererがNoneになる) 30 let renderer = self.renderer.take().expect("renderer is none"); 31 // クロージャーをコールする 32 // FnOnceクロージャーは環境を無名関数内へムーブして消費する 33 renderer() 34 } 35} 36 37struct App { 38 // Rcは参照カウント式のスマートポインター 39 // Rcを使うと1つの値に対して複数の所有者を持たせられる 40 // ここでは、所有者の一人はApp、もう一人はCardが持つrendererクロージャーになる 41 // 42 // Rustでは自己参照構造体は作れないので 43 // self -> card -> renderer -> self -> presenterのように 44 // selfから始まる参照の連鎖の先にselfを含めることはできない 45 // Rcを使って、self.presenterとcard.redererの両方をPresenterの所有者にさせる 46 presenter: Rc<Presenter>, 47 // クロージャーの型はコンパイラーが自動生成する匿名型になるので、ここでFの型に 48 // その具象型を指定できない。トレイトオブジェクト型にしておく 49 card: Card<Box<dyn FnMut() -> String>>, 50} 51 52impl App { 53 pub fn new() -> Self { 54 Self { 55 presenter: Rc::new(Presenter), 56 card: Card::new(None), 57 } 58 } 59 60 // &selfを&mut selfに変更 61 pub fn setup(&mut self) { 62 // presenterを所有するためのRCポインターを作る 63 let my_presenter = Rc::clone(&self.presenter); 64 // そのRcポインターをクロージャーの環境にムーブすることで、自己参照を回避する 65 self.card = Card::new(Some(Box::new(move || my_presenter.get()))); 66 } 67 68 pub fn update(&mut self) { 69 println!("{:?}", self.card.render()); 70 } 71} 72 73fn main() { 74 let mut app = App::new(); 75 app.setup(); 76 app.update(); 77}

発生していたエラーについて説明します。

console

1error[E0507]: cannot move out of `*self.renderer` which is behind a shared reference 2 --> src/main.rs:24:9 3 | 424 | (*self.renderer)()

(*self.renderer)()でクロージャ実行をしたいが共有参照&mutが関与しているのが原因なのかFのムーブが関与しているのが原因なのか、型サイズが不明なのが原因なのか、クロージャが呼べません。

Fのムーブが関与しています。

クロージャーは捕捉した環境と無名関数からなるデーター構造です。FnOnceクロージャーをコールすると、環境の所有権が無名関数に移動し、消費されます。そのため1度だけコールできます。上のエラーは、環境をself.renderフィールドから、無名関数へムーブしなければならないのに、selfが不変参照&の先にありrenderの値がムーブできないために起こっています。

エラーを解消するには、上のコードのようにOption::takeでムーブする、または、FnOnceをやめて、FnMutなどの他のクロージャーに変える必要があります。(FnMutは環境の可変参照&mutをとり、Fnは環境の不変参照&をとりますので、環境をムーブしません)

console

1error: lifetime may not live long enough 2 --> src/main.rs:41:31 3 | 440 | pub fn setup(&mut self) { 5 | - let's call the lifetime of this reference `'1` 641 | self.card = Card::new(&|| self.presenter.get()); 7 | ^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`

self.cardに、self.presenterという自分(self)への参照をもつクロージャーをセットしようとしていますが、SafeなRustでは、このような自己参照構造体を作れません。それぞれの&selfが異なるライフタイムを持つことが求められ、矛盾が生じてコンパイルエラーになります。

エラーを解消するには、上のコードのようにRcなどを使って自己参照を断ち切る必要があります。

console

1error[E0716]: temporary value dropped while borrowed 2 --> src/main.rs:41:32 3 | 433 | impl<'a> App<'a> { 5 | -- lifetime `'a` defined here 6... 741 | self.card = Card::new(&|| self.presenter.get()); 8 | -----------------------^^^^^^^^^^^^^^^^^^^^^^^- 9 | | | 10 | | creates a temporary which is freed while still in use 11 | assignment requires that borrow lasts for `'a` 1242 | } 13 | - temporary value is freed at the end of this statement

ライフタイムの関係でクロージャ内の一時変数がドロップされるのが理由なのか、構造体にそのクロージャを持たせたいのですが上手くできません。

クロージャーに所有者がいないためにライフタイム関係のエラーになっています。

Rustの値は所有者がいる間だけ存在できます。参照&は、誰か他に所有者がいる値を一時的に借用するためのものです。上のコードではクロージャーを作って、それの参照をCardに持たせています。しかし、クロージャーの所有者がいませんので、この式を抜けるとクロージャーがドロップされ、参照が無効(ダングリングポインター)になってしまいます。

エラーを解消するには、クロージャーをCardに所有させる必要があります。

投稿2022/04/05 15:28

編集2022/04/05 23:36
tatsuya6502

総合スコア2046

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

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

akira_kano1101

2022/04/06 03:38

やりたかったことはこれです。 質問、二つとも腑に落ちました。 非常に助かりました。分かりやすかったです。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問