Rustコトハジメ

プログラミング言語Rustと競プロに関する情報をお届けします。

From実装からInto実装は自動生成されるが逆はされない

FromトレイトとIntoトレイトは、所有権を食べて他のオブジェクトに変換するトレイトです。これは、Derefトレイトのように自動的に変換されることはありませんが、AsRefトレイトのように、関数をより一般化するために定義されています。プログラミングRustには、これらのトレイトが定義される前には、型を他の型に変換するアドホックな実装が溢れていたため標準ライブラリが提供することにしたと書いてあります。

pub trait From<T>: Sized {
    fn from(_: T) -> Self;
}

pub trait Into<T>: Sized {
    fn into(self) -> T;
}

FromとIntoについて自明に思えることは、impl From<S> for Tが定義されていれば、impl Into<T> for Sも定義されるでしょうということです。両方ともSからTへの変換について定義しているからです。しかし実際にはFromからIntoは自動生成されるもののIntoからFromは自動生成されません

ドキュメントから確認する

Intoの公式のドキュメントを見て確認します。

std::convert::Into - Rust

  • Library authors should not directly implement this trait, but should prefer implementing the From trait, which offers greater flexibility and provides an equivalent Into implementation for free, thanks to a blanket implementation in the standard library. Intoは実装しなくていい。むしろFromを実装せよ。そうすれば、Intoの実装も手に入る。
  • There is one exception to implementing Into, and it's kind of esoteric. If the destination type is not part of the current crate, and it uses a generic variable, then you can't implement From directly. for XXXの部分に自分のcrate外の型を入れることは出来ない。もししたい場合は例外的に、Intoを実装する。
  • This won't always allow the conversion: for example, try! and ? always use From. However, in most cases, people use Into to do the conversions, and this will allow that. try!?は内部的にfromを使っているから困ったことになることもある。しかしこれはむしろ例外で、通常はintoを使うから大丈夫。(じゃあなんでtry!?はintoを使わないのか?)
  • In almost all cases, you should try to implement From, then fall back to Into if From can't be implemented. だからプラクティスとしては、まずはFromを実装することから考えて、出来ないならIntoを実装することを考えよう。

上でいう"blanket implementation"というのは以下のことを言ってます。

// From implies Into
impl<T, U> Into<U> for T where U: From<T>
{
    fn into(self) -> U {
        U::from(self)
    }
}

理由を考える

理由を考察します。おそらく、FromからIntoに対する実装だけがあり、逆がないのは、今実装している型Tに対して、impl XXX for Tを実装するのであれば、XXXはIntoよりFromの方が便利だからでしょう。なぜかというと、今実装しているモジュールはTに関するものなので、他の型をTに持ってきて何か処理をするというコードを書きたいからです。

実験コード

イメージが湧きにくい人のために実験コードも書きました。

use std::convert::From;

#[derive(Copy, Clone)]
struct S { x: i32 }
#[derive(Copy, Clone)]
struct T { x: i32 }

// S -> T 

/**
impl Into<T> for S {
    fn into(self) -> T {
        T { x: self.x }
    }
}
**/

impl From<S> for T {
    fn from(s: S) -> T {
        T { x: s.x }
    }
}

fn main() {
    let s = S { x: 5 };
    let t0: T = s.into();
    let t1 = T::from(s); // this won't compile with Into
}

Fromの方をコメントアウトして、Intoの実装を採用した場合、t1の行はコンパイル出来ません。

27 |     let t1 = T::from(s); // this won't compile with Into
   |                      ^ expected struct `T`, found struct `S`