構造体生成時に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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/24 01:39
回答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
総合スコア468
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/24 01:10
2020/06/24 02:46
2020/06/24 04:04
0
Main
をSelf
に修正すれば、こういうエラーが出ます:
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
を型引数にして、func
をF
にすれば、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}
一番簡単な解決法は、new
をimpl
の外で定義すれば良いです。
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_a
をWorker
に置きたいのなら、型引数を除いて、関数ポインタ型の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}
Worker
をstruct
ではなく、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総合スコア464
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/24 04:33
2020/06/24 04:37
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()
の戻り値型はSelf
(Worker<F>
)ですので、ジェネリクスの型パラメータF
を含んでます。そしてF
の具体的な型はnew()
を呼び出す側で決まります。つまり、new()
は呼び出し側が求める、どんなWorker<何かの型>
でも返せなければなりません。
一方、|x| func_a(x)
は具体的な型を持ちます。もしこれがコンパイルできたとすると、new()
の戻り値はWorker<具体的な一つの型>
になり、Self
と矛盾してしまいます。そのため型不一致のエラーE0308になるわけです。
解決法としては2つあると思います。
- クロージャを
new()
の外側から与える 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総合スコア2046
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。