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

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

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

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

Q&A

解決済

1回答

1078閲覧

トレイトオブジェクトのコンストラクタを作りたい

hito4

総合スコア10

Rust

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

0グッド

0クリップ

投稿2020/03/03 14:37

編集2020/03/03 14:38

前提・実現したいこと

Rustでトレイトオブジェクトを作るコンストラクタは、どのように記述するのでしょうか。
下記コードのStructAがOKで、StructBがNGである理由がなかなか理解できず…。

Self、オブジェクト安全性 などをキーワードにしてネットや書籍で調べたのですが、
コンストラクタの書き方を見つけられませんでした。
定番の書き方があれば教えて頂けないでしょうか。

該当のソースコード

Rust

1fn main() { 2 //この記述はOK 3 let x: Box<dyn TraitA> = Box::new( StructA {} ); 4 5 //このように記述したいが、コンパイルエラーになる 6 let y: Box<dyn TraitB> = Box::new( StructB::new() ); 7} 8 9struct StructA {} 10struct StructB {} 11 12trait TraitA {} 13trait TraitB { 14 fn new() -> Self; 15} 16 17impl TraitA for StructA {} 18impl TraitB for StructB { 19 fn new() -> Self { 20 Self {} 21 } 22}

発生している問題・エラーメッセージ

error[E0038]: the trait `TraitB` cannot be made into an object --> src/main.rs:6:12 | 6 | let y: Box<dyn TraitB> = Box::new( StructB::new() ); | ^^^^^^^^^^^^^^^ the trait `TraitB` cannot be made into an object ... 14 | fn new() -> Self; | --- associated function `new` has no `self` parameter error[E0038]: the trait `TraitB` cannot be made into an object --> src/main.rs:6:30 | 6 | let y: Box<dyn TraitB> = Box::new( StructB::new() ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TraitB` cannot be made into an object ... 14 | fn new() -> Self; | --- associated function `new` has no `self` parameter | = note: required because of the requirements on the impl of `std::ops::CoerceUnsized<std::boxed::Box<dyn TraitB>>` for `std::boxed::Box<StructB>` = note: required by cast to type `std::boxed::Box<dyn TraitB>` error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0038`.

補足

本質問は、以下の質問で頂いた回答から派生しました。
https://teratail.com/questions/244294?nli=5e5c3e7f-5198-4ba9-acc7-4de50a280215

補足情報(FW/ツールのバージョンなど)

OS: Windows10 + WSL1 + Ubuntu
Rust:1.41.0

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

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

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

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

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

guest

回答1

0

ベストアンサー

下記コードのStructAがOKで、StructBがNGである理由がなかなか理解できず…。

こういうときは、まずエラーの解説を読んでみるのがおすすめです。

error[E0038]: the trait TraitB cannot be made into an object

コンパイラが出力する多くのエラーにはE0038のような番号がついています。この番号を使って、エラーの詳細な解説を表示できます。Webブラウザで https://doc.rust-lang.org/error-index.html#E0038 を開くか、ターミナルから rustc --explain 38 を実行します。英語による説明ですが、最近は機械翻訳の性能向上が著しいので、それらを活用すればなんとかなりそうです。(例:https://www.bing.com/translator?from=&to=ja

E0038の解説では、どういうトレイトがトレイトオブジェクトにできないのか、いくつかの条件が書かれています。今回のTraitBが当てはまるのは以下の条件です。

  • Self型の引数をとったり、Self型の戻り値を返したりするトレイトメソッドがある。(メソッドレシーバを示す&selfなどは問題ない)
  • メソッドレシーバ(&self, &muf self, self)を持たないトレイトメソッドがある

これらがなぜダメなのかの理由を説明しだすと長くなるので、ここでは省略します。エラーの解説を機械翻訳にかけてみてください。また、いくつかの項目については、ここ で和訳されています。

定番の書き方があれば教えて頂けないでしょうか。

定番かどうかはわかりませんが、TraitBにコンストラクタにあたるトレイトメソッドを定義するのではなく、別のトレイトを使用する方法があります。ご質問のコードのnew()に該当するトレイトメソッドは、標準ライブラリのDefaultトレイトですでに定義されていますので、それを使うといいでしょう。Defaultトレイトの定義は以下の通りです。

https://doc.rust-lang.org/std/default/trait.Default.html

rust

1pub trait Default { 2 fn default() -> Self; 3}

これをStructBに実装すると以下のようになります。

rust

1// deriveアトリビュートでDefaultトレイトを自動導出する 2#[derive(Default)] 3struct StructB {} 4 5// 自動導出せずに手で実装することもできる 6// impl Default for StructB { 7// fn default() -> Self { 8// Self {} 9// } 10// } 11 12trait TraitB { 13 // トレイトメソッドがなにもないと例としてさみしいので適当なものを定義 14 fn foo(&self); 15} 16 17impl TraitB for StructB { 18 fn foo(&self) {} 19} 20 21fn main() { 22 // default()メソッドを使う 23 let y: Box<dyn TraitB> = Box::new( StructB::default() ); 24}

このように引数をとらないコンストラクタならDefaultトレイトで実現できます。もしいくつかの共通の引数をとるようなコンストラクタが必要なら、TraitBとは別のトレイトを定義してみてください。

追記

質問からは逸れますが Box::new(StructB::default())のような値をbox化するコードを頻繁に書く場合は、StructBにbox化するメソッド(またはトレイトメソッド)を実装すると少し書きやすくなりそうです。(こちらで紹介されているアイデアです)

rust

1impl StructB { 2 fn boxed(self) -> Box<Self> { 3 Box::new(self) 4 } 5 6 fn boxed_new() -> Box<Self> { 7 Self::default().boxed() 8 } 9} 10 11fn main() { 12 let mut y: Box<dyn TraitB>; 13 // y = Box::new(StructB::default()) の代わりに以下のように書ける 14 y = StructB::default().boxed(); 15 y = StructB::boxed_new(); 16}

この例ではboxed(self)boxed_new()を何かのトレイトのメソッドにする理由は特になかったので、Structのメソッドや関連関数として実装しました。もしトレイトのメソッドにする必要があれば、(TraitBとは別にトレイトを定義すれば)そうすることも可能です。

投稿2020/03/03 15:44

編集2020/03/03 23:42
tatsuya6502

総合スコア2035

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

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

hito4

2020/03/04 12:49 編集

詳しく説明していただき、とても勉強になりました。 本当にありがとうございます。 エラーコードの調べ方、実は知りませんでした…(汗)。 オブジェクト安全性は、難しそうですね。 ソースコードですが、TraitBとは別のTraitCを作り、その中に教えてもらった「box化するメソッド」を書くことで、とてもすっきりしたコードになったと思います。 活用させていただきます m(_ _)m 。 fn main() { let z: Box<dyn TraitB> = StructB::boxed_new(); println!("{:#?}",z) } #[derive(Default,Debug)] struct StructB { a: u64, b: u64, } trait TraitB:std::fmt::Debug {} trait TraitC { fn boxed(self) -> Box<Self>; fn boxed_new() -> Box<Self>; } impl TraitB for StructB {} impl TraitC for StructB { fn boxed(self) -> Box<Self> { Box::new(self) } fn boxed_new() -> Box<Self> { let mut z = Self::default().boxed(); z.a = 100; z.b = 200; z } }
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問