[Rust]クロージャの引数の型の省略と高階トレイト境界

作成日:2022.12.11

タグ: Rust

はじめに

 本記事はバージョン1.65.0のrustcのもとで書かれている。
 Rustのコンパイラはとても親切だが、それでもライフタイムに関するエラーは難しい。 今回、省略されたクロージャの引数の参照型のライフタイムについて高階トレイト境界(HRTB)がらみではまったので紹介する。

目次

  1. 遭遇した問題
  2. 解決方法
  3. 原因
  4. まとめ

遭遇した問題

 例えば以下のコードは動く。

fn main() {
    test(|_| {});
}
fn test(_: impl Fn(&i32)) {}

 しかし同じ気持ちで以下のコードを書くとコンパイルエラーとなる。

fn main() {
    let f = |_| {};
    test(f);
}
fn test(_: impl Fn(&i32)) {}

error[E0308]: mismatched types
 --> src\main.rs:3:5
  |
3 |     test(f);
  |     ^^^^^^^ one type is more general than the other
  |
  = note: expected trait `for<'r> Fn<(&'r i32,)>`
             found trait `Fn<(&i32,)>`
note: this closure does not fulfill the lifetime requirements
 --> src\main.rs:2:13
  |
2 |     let f = |_| {};
  |             ^^^
note: the lifetime requirement is introduced here
 --> src\main.rs:5:17
  |
5 | fn test(_: impl Fn(&i32)) {}
  |                 ^^^^^^^^

error: implementation of `FnOnce` is not general enough
 --> src\main.rs:3:5
  |
3 |     test(f);
  |     ^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 i32)` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2`

解決方法

 引数の型を省略せずに書く、より正確には参照型であることを明示すればコンパイルが通るようになる。

let f = |_: &i32| {};

let f = |_: &_| {};

原因

 このUsers Forumの返信によると、ほとんどの場合で高階トレイト境界は導入されないようだ。 実際、エラーメッセージには

  = note: expected trait `for<'r> Fn<(&'r i32,)>`
             found trait `Fn<(&i32,)>`

とあり、このことを示唆している。これはエラーメッセージの最後の2行にもあるように、任意のライフタイムをとれることが要求されているが、あるライフタイムしかとれないということである。したがって意味は変わってしまうがtest関数を

fn test<'a>(_: impl Fn(&'a i32)) {}

と置き換えるとコンパイルできる。

 逆に最初にあげたコードがコンパイルできるのは、先にあげたForumでも述べられているように、Fnトレイトが境界として明示された関数の引数にクロージャを直接渡したときは高階トレイト境界を含む型が推論されるからのようだ。

fn main() {
    test(id(|_| {}));   // implementation of `FnOnce` is not general enough
}
fn test(_: impl Fn(&i32)) {}
fn id<T>(x: T) -> T {
    x
}

このことは上記のようにクロージャをid関数に渡してからtest関数に渡すコードがコンパイルエラーになることからも推察される。 また、そもそも先にあげたForumではSomeに包まれたクロージャに対して上手く型推論されないという話である。

まとめ

 クロージャの引数は型を省略できるが、参照型を省略するとほとんどの場合高階トレイト境界が導入されない。 ただし、Fnトレイトを境界とする型をとる引数に直接クロージャを渡したときは例外である。

タグ「Rust」の記事

前の記事

[Rust] 一時変数に関する非直感的な挙動

2022.11.27 作成 2023.01.27 更新

式の中で作られた構造体はその式の評価後すぐdropされるとは限らない

最新記事

当記事が最新記事です。

自己紹介

プログラミングとか合成音声とか
詳しくはこちら
twitter

プライバシーポリシー

個人情報利用についてはこちら

最終更新日:2023.03.05

お問い合わせ

このページに関するお問い合わせはこちら

ブログMENU

タグ

全体MENU

寄付モドキ

0冂から10000冂まで
詳しくはこちら