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

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

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

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

Q&A

解決済

1回答

897閲覧

「AsRefを入力とする関数」を引数に取る関数の呼び出しでコンパイルエラーになる理由・ならない理由が分からない

Toru3

総合スコア32

Rust

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

1グッド

1クリップ

投稿2020/06/06 07:25

前提・実現したいこと

参照の引数を持つ関数を別の関数に渡したいの中で何故かクロージャーを渡した場合にコンパイルが通る理由が分かりませんでした。
以下のように1~4まで試してみましたが、何故か2と3はコンパイルエラーとなりました。特に3と4はなぜ差が出るのが検討もつきませんでした。

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

Compiling playground v0.0.1 (/playground) error[E0631]: type mismatch in function arguments --> src/main.rs:20:22 | 5 | fn f2<T: AsRef<[u32]>>(input: T) -> u32 { | --------------------------------------- found signature of `fn(_) -> _` ... 9 | fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32 { | ----------------- required by this bound in `g` ... 20 | println!("{}", g(f2)); | ^^ expected signature of `for<'r> fn(&'r [u32]) -> _` error[E0271]: type mismatch resolving `for<'r> <fn(_) -> u32 {f2::<_>} as std::ops::FnOnce<(&'r [u32],)>>::Output == u32` --> src/main.rs:20:20 | 9 | fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32 { | --- required by this bound in `g` ... 20 | println!("{}", g(f2)); | ^ expected bound lifetime parameter, found concrete lifetime error[E0631]: type mismatch in closure arguments --> src/main.rs:24:22 | 9 | fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32 { | ----------------- required by this bound in `g` ... 23 | let h = |x| f2(x); | --------- found signature of `fn(_) -> _` 24 | println!("{}", g(h)); | ^ expected signature of `for<'r> fn(&'r [u32]) -> _` error[E0271]: type mismatch resolving `for<'r> <[closure@src/main.rs:23:13: 23:22] as std::ops::FnOnce<(&'r [u32],)>>::Output == u32` --> src/main.rs:24:20 | 9 | fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32 { | --- required by this bound in `g` ... 24 | println!("{}", g(h)); | ^ expected bound lifetime parameter, found concrete lifetime error: aborting due to 4 previous errors Some errors have detailed explanations: E0271, E0631. For more information about an error, try `rustc --explain E0271`. error: could not compile `playground`. To learn more, run the command again with --verbose.

該当のソースコード

Rust

1fn f1(input: &[u32]) -> u32 { 2 input[0] 3} 4 5fn f2<T: AsRef<[u32]>>(input: T) -> u32 { 6 input.as_ref()[0] 7} 8 9fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32 { 10 let input = vec![1, 2, 3]; 11 let output = func(&input); 12 output 13} 14 15fn main() { 16 // 1. ok 17 println!("{}", g(f1)); 18 19 // 2. error 20 println!("{}", g(f2)); 21 22 // 3. error 23 let h = |x| f2(x); 24 println!("{}", g(h)); 25 26 // 4. ok 27 println!("{}", g(|x| f2(x))); 28}

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

コードはRust Playgroundにもあります。

tatsuya6502👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

HRTBまわりの問題だと思われます。HRTBまわりの挙動はぼくも正確に理解できているわけではないのですが、以下の推測で大きくは外していないと思います。

まず関数 g が受け取っている F の制約。ここは fn 以外で特別にlifetime elisionが有効な箇所で、展開すると以下のような制約になります。

rust

1// オリジナル 2fn g<F: Fn(&[u32]) -> u32>(func: F) -> u32; 3// lifetime elisionの展開後 4fn g<F: for<'a> Fn(&'a [u32]) -> u32>(func: F) -> u32; 5// forを手前に持ってくる 6fn g<F>(func: F) -> u32 7where 8 for<'a> F: Fn(&'a [u32]) -> u32;

つまり、「任意の 'a に対して F: Fn(&'a [u32]) -> u32 が成立していること」がここでは求められています。

このようにライフタイムに関する高階の制約をRustではある程度扱うことができますが、この部分の動作はかなりアドホックです。

rust

1fn main() { 2 // ライフタイム量化の削除は可能 3 let f: fn(&str) -> &[u8] = AsRef::as_ref; 4 let f: fn(&'static str) -> &'static [u8] = AsRef::as_ref; 5 6 // ライフタイム量化が増えるような変換は不可能 (意味的に可能であっても) 7 let f: fn(i32) -> i32 = std::convert::identity; 8 // let f: fn(&i32) -> &i32 = std::convert::identity; // error 9 10 // fn/クロージャを作成するタイミングで量化を増やせるので、η展開すれば量化を増やせる 11 let f: fn(&i32) -> &i32 = |x| std::convert::identity(x); 12}

コンパイラの実装上は、 identity を見つけたときにその型引数に型変数を代入することで型推論を行います。上記の // error と書いてあるコードが通るためにはこのとき for<'a> ?A<'a> という形の型変数を作る必要がありますが、そうはしません。理由は正確にはわかりませんが、このように式の出現でいちいち量化を検討すると問題が複雑になり、コンパイラパフォーマンスにも影響があるのではないかと思います。

ではどこでは量化が可能なのか。関数定義ではいつも以下のように普通に使っていると思いますが……

rust

1// ライフタイムに関して量化されている 2fn foo(s: &str) -> &[u8]; 3// fn foo<'a>(s: &'a str) -> &'a [u8];

クロージャでも同様にライフタイムによる量化が可能になっています。ただし、この場合は引数型が省略できるので、何らかの方法で「どう量化するべきか」を推測できる必要があります。

rust

1fn foo<'a>(i: &'a i32) -> &'a i32 { 2 // このfは以下のどちらかだが、これを確定する根拠はない (どちらにも合理性がある): 3 // f: impl Fn(&'a i32) -> &'a i32 4 // f: impl for<'b> Fn(&'b i32) -> &'b i32 5 let f = |j| j; 6 f(i) 7}

実際にどちらに推測されたかは、以下のように書いてみればわかります。

rust

1fn foo<'a>(i: &'a i32) -> &'a i32 { 2 let f = |j| j; 3 let result = f(i); 4 5 let x = 42; 6 f(&x); // error 7 8 result 9}

上記の場合は単相に推論されたことがわかります。

一方、 f の型として多相な関数ポインタ型を指定していると、多相に推論されます。

rust

1fn foo<'a>(i: &'a i32) -> &'a i32 { 2 let f: fn(&i32) -> &i32 = |j| j; 3 let result = f(i); 4 5 let x = 42; 6 f(&x); // OK 7 8 result 9}

Rustの型推論では、トップダウンに「期待する型」を伝搬することで、このようなアドホックな処理のヒントにしています (型強制を行うタイミングの決定などにも使っている)。上記の例では、「期待する型」が関数ポインタであるときに、それを利用して多相性を推論しているのだと考えられます。


あらためて元の問題に戻ると、

  • 2のケースでは、 f2T には特定のライフタイムが入った単相型 &'_ u32 が代入される。こうして得られた関数は g が期待する型 (impl for<'a> Fn(&'a [i32]) -> i32) と互換ではないのでエラーになっている。
  • 3~4のケースではクロージャで挟まれることで、この部分でライフタイム量化が期待通りに処理される可能性が生じる。しかしクロージャのライフタイム量化はアドホックに決定されるので、型のヒントが適切に与えられている4のケースのみ通る

と考えられます。

投稿2020/06/06 08:45

qnighy

総合スコア210

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

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

Toru3

2020/06/06 09:49

なるほど、多相なものを要求しているところに単相なものを渡しているからエラーになっていて、3のケースではhに代入した時点で単相に推論されてしまうのでエラーになるということですね。そして4はクロージャーの生成時に増せる量化がうまく働いて多相なものとして渡るのでエラーにならないということですね。確かに3で多相な関数ポインタ型を指定するとエラーになりませんでした。 スッキリしました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問