北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

impl Trait 型の値を再代入しようとしてハマった

impl トレイト名Rust で存在型を扱うのに使える 。しかし、以下はコンパイルが通らない。

use std::fmt::Display;

fn f<T>(t: T) -> impl Display
where
    T: Display,
{
    t
}

fn main() {
    let mut s = f("");
    s = f(s);
}
   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:12:11
   |
12 |     s = f(s);
   |           ^ expected `&str`, found opaque type
   |
   = note: expected reference `&str`
            found opaque type `impl std::fmt::Display`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

以下なら通る。

use std::fmt::Display;

fn f(t: Box<dyn Display>) -> impl Display
{
    t
}

fn main() {
    let mut s = f(Box::new(""));
    s = f(Box::new(s));
}

これは、 impl トレイト という型は、関数定義ごとにそれぞれ違う型になるため。例えば、

use std::fmt::Display;

fn f(s: &'static str) -> impl Display { s }

fn g(s: &'static str) -> impl Display { s }

fn main() {
    let mut s = f("1");
    // s = g("2");  // コンパイルできない
    s = f("3");
}

というコードだと、 f の戻り値と g の戻り値は別の型となるので、 f の戻り値を代入した mutable 変数 sg の戻り値を代入することは許されない。

さて、最初の例に戻ると、 f<T>(t: T) -> impl Display は型変数 T を持っている。よって、型変数ごとに関数は別のものとなる。 sf::<&str> の戻り値が代入されているので、 = で再代入する際も右辺は f::<&str> でなければ型が合わない。よって、その引数は &str でなければならないが、 s の型は ( f::<&str> の戻り値である) impl Display であるため、冒頭のエラーメッセージが表示される。

詳しくは、以下の qnighy さんの2年前のエントリの「同一性」の部分に書かれている。

qnighy.hatenablog.com