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

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

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

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

Q&A

解決済

3回答

525閲覧

structをnewする時にannotationsを要求されるが指定方法がわからない

namuyan

総合スコア76

Rust

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

0グッド

0クリップ

投稿2020/06/23 14:55

編集2020/06/24 01:44

構造体生成時にFが要求されますが指定方法がわかりません。

下記に問題を単純化したコードを載せています。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker<F: Fn(&[u8]) -> Vec<u8>> { 6 func: F, 7} 8 9impl<F: Fn(&[u8]) -> Vec<u8>> Worker<F> { 10 fn new() -> Self { 11 Worker { 12 func: |x| func_a(x), 13 } 14 } 15} 16 17fn main() { 18 // let worker = Worker::<F>::new(); 19 let worker = Worker::new(); 20}

Playground1
Playground2 -duck typing=
Playground3

恐らくWorker::<F>::new()のような形で指定すると思われますがどのように書けばいいのかわかりません。何かわかる方は回答の方をよろしくお願いします。

追記 6/24

元のコードPlayground1ではmain()を除いてもコンパイルできませんでした。そこでDuck typingを用いたオリジナルのコードにさらに近づけて修正しました。

さらに追記

Duck typingが問題に絡んでいると思っていましたが関係なかったので修正を戻しました。

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

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

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

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

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

tatsuya6502

2020/06/23 22:12

new() 関連関数の本体に Main という構造体が出てきますが、Self の間違いでしょうか? コードの修正をお願いします。
namuyan

2020/06/24 01:39

簡単な型ミスを直したところコンパイル通らず、上手くいかない原因はDuck typingを除いたことにあると思ってしまったのですが余計な修正をしてしまいましたね。
guest

回答3

0

こんにちは。提示されたコードはジェネリクスパラメータの指定以外にもコンパイルが通らない箇所があるので、それを直したコードがこちらになります。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker<F: Fn(&[u8]) -> Vec<u8>> { 6 func: F, 7} 8 9impl<F: Fn(&[u8]) -> Vec<u8>> Worker<F> { 10 fn new() -> Self { 11 Self { 12 func: |x| func_a(x), 13 } 14 } 15} 16 17fn main() { 18 // let worker = Worker::<F>::new(); 19 let worker = Worker::new(); 20}

これをベースに回答します。
Worker::new は内部でクロージャを生成しているので、それに対応する型はただ1つです。
一方、 impl Worker では F を型パラメータで受け取っているので、外部から渡されるあらゆる型への対応が求められます。この2点が矛盾しているのでコンパイルエラーになっています。
これを解決する一般的方法が2つ、そして今回提示されたサンプルコードのみで使えるテクニックが1つあります。

1. ジェネリクスパラメータをやめる

Worker をジェネリクスにするのをやめて、トレイトオブジェクトを作れば問題は起きなくなります。そのときのコードはこちら

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5// ジェネリクスでない 6struct Worker { 7 // トレイトオブジェクトにする 8 func: Box<dyn Fn(&[u8]) -> Vec<u8>>, 9} 10 11impl Worker { 12 fn new() -> Self { 13 Self { 14 func: Box::new(|x| func_a(x)), 15 } 16 } 17} 18 19fn main() { 20 // let worker = Worker::<F>::new(); 21 let worker = Worker::new(); 22}

トレイトオブジェクトについて分からなかったら公式ドキュメントがありますが、いささか分かりづらいかもしれません

2. impl Worker をやめる

「返り値の型が唯一決まっている」場合には impl Trait 構文が使えます。ただし、ジェネリクス構造体の impl には使えないので、 Worker::new を独立した関数として定義してあげます。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker<F: Fn(&[u8]) -> Vec<u8>> { 6 func: F, 7} 8 9// 独立した関数として定義すると `Worker` のパラメータに `impl Fn` と書ける 10fn new_worker() -> Worker<impl Fn(&[u8]) -> Vec<u8>> { 11 Worker { 12 func: |x| func_a(x), 13 } 14} 15 16fn main() { 17 // let worker = Worker::<F>::new(); 18 let worker = new_worker(); 19}

3. fn() 型を使う

今回の例に限って言えば Worker::new の中で作っているクロージャは、環境にある変数をキャプチャしていません。そのような場合にはクロージャを fn() (プリミティブの関数)にキャストできます。
その機能を用いてこう書けます。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker<F: Fn(&[u8]) -> Vec<u8>> { 6 func: F, 7} 8 9// Workerのパラメータを Fn (トレイト) ではなく fn (プリミティブの型)にする 10impl Worker<fn(&[u8]) -> Vec<u8>> { 11 fn new() -> Self { 12 Worker { 13 func: |x| func_a(x), 14 } 15 } 16} 17 18fn main() { 19 // let worker = Worker::<F>::new(); 20 let worker = Worker::new(); 21}

以上のどれかで解決するのではないかと思われます。
適切に問題を解決すると思われる順に並べたつもりです。

1は一番汎用性が高いですが、トレイトオブジェクトを作っているので若干のオーバーヘッドが乗ります。
2はオーバーヘッドは乗りませんが、 new に関する問題だけをアドホックに解決したので他の箇所でも同様の問題が起きる可能性があります。私見ですが Worker という名前からして他の箇所でも同様の問題が起きる確率は多そうに見えます。
3はオーバーヘッドなく実現できていますが、今回の例に限った解決策です。クロージャの定義を変えただけで動かなくなる可能性があります。

以上のどれかで解決すれば幸いです。

投稿2020/06/24 01:04

blackenedgold

総合スコア468

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

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

namuyan

2020/06/24 01:10

申し訳ありません、ちょうど修正作業と回答が重なってしまったようです。もちろんこちらの回答も読ませていただきます。
tatsuya6502

2020/06/24 02:46

2. のようにすると impl Trait 構文が使えるんですね。勉強になりました!
YufanLou

2020/06/24 04:04

3のようにも具体的な型を使えますね。勉強になりました!
guest

0

MainSelfに修正すれば、こういうエラーが出ます:

error[E0308]: mismatched types --> src/main.rs:12:19 | 9 | impl<F: Fn(&[u8]) -> Vec<u8>> Worker<F> { | - this type parameter ... 12 | func: |x| func_a(x), | ^^^^^^^^^^^^^ expected type parameter `F`, found closure | = note: expected type parameter `F` found closure `[closure@src/main.rs:12:19: 12:32]`

expected type parameter F, found closure

「型引数Fを期待しているが、closureを見つけた」と述べています。なぜなら、Fを型引数にして、funcFにすれば、funcも引数にしなければなりません。例えば、このコードも同じエラーを出します:

rust

1use std::fmt::Debug; 2 3struct S<T: Debug> { 4 d: T 5} 6 7impl<T: Debug> S<T> { 8 fn new() -> Self { 9 S { 10 d: 8 11 } 12 } 13}

一番簡単な解決法は、newimplの外で定義すれば良いです。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker<F: Fn(&[u8]) -> Vec<u8>> { 6 func: F, 7} 8 9fn new_worker() -> Worker<impl Fn(&[u8]) -> Vec<u8>> { 10 Worker { func: |x| func_a(x) } 11} 12 13fn main() { 14 let worker = new_worker(); 15 (worker.func)(&[3]); 16}

もしただfunc_aWorkerに置きたいのなら、型引数を除いて、関数ポインタ型のfnを使えばいいと思います。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker { 6 func: fn(&[u8]) -> Vec<u8>, 7} 8 9impl Worker { 10 fn new() -> Self { 11 Self { 12 func: |x| func_a(x), 13 } 14 } 15} 16 17fn main() { 18 let worker = Worker::new(); 19 let worker1 = Worker { func: |x| Vec::new() }; 20}

ご覧の通り、closureが外の変数を使わなければ、関数ポインタのように使えます。

もちろん、関数ポインタを直接に使うこともできます。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5fn use_func(func: fn(&[u8]) -> Vec<u8>) { 6 let v = func(&[3]); 7} 8 9fn main() { 10 use_func(|x| func_a(x)); 11}

Workerstructではなく、traitにする書き方もあります。そうすれば、Workerを trait object として使えます。そしてそれぞれのimplに違うデータを付けることが可能です。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5trait Worker { 6 fn func(&self, input: &[u8]) -> Vec<u8>; 7} 8 9struct WorkerImpl(u8); 10 11impl Worker for WorkerImpl { 12 fn func(&self, input: &[u8]) -> Vec<u8> { 13 let data = self.0; 14 func_a(input) 15 } 16} 17 18fn use_worker(w: Box<dyn Worker>) { 19 let v = w.func(&[3]); 20} 21 22fn main() { 23 let worker = Box::new(WorkerImpl) as Box<dyn Worker>; 24 use_worker(worker); 25}

本気でclosureが必要だとしたら、Boxでclosureをヒープに置ければ大丈夫です。こちらもdynキーワードでclosureを trait object にしています。

rust

1fn func_a<T: AsRef<[u8]>>(input: T) -> Vec<u8> { 2 unimplemented!() 3} 4 5struct Worker { 6 func: Box<dyn Fn(&[u8]) -> Vec<u8>>, 7} 8 9impl Worker { 10 fn new() -> Self { 11 Self { 12 func: Box::new(|x| func_a(x)) 13 } 14 } 15 fn with_func(func: Box<dyn Fn(&[u8]) -> Vec<u8>>) -> Self { 16 Self { 17 func 18 } 19 } 20} 21 22fn main() { 23 let worker1 = Worker::with_func(Box::new(|x| Vec::new())); 24 (worker1.func)(&[3]); 25 let worker = Worker::new(); 26 (worker.func)(&[3]); 27}

長いですが、お役に立てたら幸いです。


修正 2020-06-24:
用語:「関数指針」を「関数ポインタ」に修正しました。

投稿2020/06/24 03:11

編集2020/06/24 04:42
YufanLou

総合スコア464

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

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

blackenedgold

2020/06/24 04:33

関数指針は関数ポインタのことでしょうか。
YufanLou

2020/06/24 04:37

そうです。中国語で「pointer」を「指針」に訳して、そのまま使えてしまいました????。ご指摘ありがとうございます。
guest

0

ベストアンサー

質問のコードに Main という構造体が出てきますが、Selfの間違いだと仮定して回答します。

質問のコードをコンパイルすると2つのエラーが出ます。

  • 型の不一致のエラー(E0308)
  • 型推論のエラー(E0284)

E0308を直すと、E0284は自然に解消します。

error[E0308]: mismatched types --> src/main.rs:12:19 | 9 | impl<F: Fn(&[u8]) -> Vec<u8>> Worker<F> { | - this type parameter ... 12 | func: |x| func_a(x), | ^^^^^^^^^^^^^ expected type parameter `F`, found closure | = note: expected type parameter `F` found closure `[closure@src/main.rs:12:19: 12:32]` = help: type parameters must be constrained to match other types = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

関連関数new()の戻り値型はSelfWorker<F>)ですので、ジェネリクスの型パラメータFを含んでます。そしてFの具体的な型はnew()を呼び出す側で決まります。つまり、new()は呼び出し側が求める、どんなWorker<何かの型>でも返せなければなりません。

一方、|x| func_a(x)は具体的な型を持ちます。もしこれがコンパイルできたとすると、new()の戻り値はWorker<具体的な一つの型>になり、Selfと矛盾してしまいます。そのため型不一致のエラーE0308になるわけです。

解決法としては2つあると思います。

  1. クロージャをnew()の外側から与える
  2. Worker.funcをジェネリクスの型パラメータではなく、具体的な型にする

1. クロージャをnew()の外側から与える

new()の中でクロージャを作るのではなく、F型の引数としてとるようにします。

rust

1impl<F: Fn(&[u8]) -> Vec<u8>> Worker<F> { 2 fn new(func: F) -> Self { 3 Self { func } 4 } 5} 6 7fn main() { 8 let worker = Worker::new(|x| func_a(x)); 9}

2. Worker.funcを具体的な型にする

Worker構造体のfuncフィールドの型を、ジェネリクスの型パラメータではなく、具体的な型にします。

|x| func_a(x)は具体的な型を持ちますが、コンパイラが内部的に作成する匿名型なので、ソースコードで型を指定することはできません。

しかし、このクロージャをよく見ると自由変数がありません(外側にある変数を環境にキャプチャしていません) こういうクロージャは普通の関数と同じように扱えますので、関数ポインタ型fn(&[u8]) -> Vec<u8>へと型強制できます。

rust

1struct Worker { 2 // ジェネリクス(Fnトレイトを実装した何かのクロージャ型)ではなく 3 // 具体的な型(関数ポインタ型)にする 4 func: fn(&[u8]) -> Vec<u8>, 5} 6 7impl Worker { 8 fn new() -> Self { 9 Self { 10 // 自由変数を持たないクロージャは関数ポインタ型に型強制できる 11 func: |x| func_a(x), 12 } 13 } 14} 15 16fn main() { 17 let worker = Worker::new(); 18}

投稿2020/06/24 00:54

編集2020/06/24 01:00
tatsuya6502

総合スコア2046

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

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

namuyan

2020/06/24 01:11

申し訳ありません、ちょうど修正作業と回答が重なってしまったようです。もちろんこちらの回答も読ませていただきます。
namuyan

2020/06/24 01:54

funcをより具体的な型に落とし込むこと(Worker.funcを具体的な型にする)で解決しました。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問