dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

RustのFromトレイトとIntoトレイト

先日こんな記事を書いたのだが、最後に登場した From トレイトというものをよく理解してなかったのでその後調べたメモ。

検索したところ Web 上では Rust by Example ぐらいしかヒットしなかった。

また、以前読んだ「実践 Rust 入門」にもさらっとだが触れられていた。

(8-6-2 std::convert::From)

概要

この2つのトレイトはどちらも、ある型から別の型に変換するためのトレイトである。

たとえば、String 型の文字列を定義するときによく使う

let s = String::from("hello");

String::from()From トレイトを利用している。

また、この2つのトレイトは密接にリンクしており、From トレイトを実装すると自動的に Into トレイトも実装されるという関係になっている。ただし逆はない。

From トレイト

From トレイトは

impl From<T> for U {
  fn from(T) -> Self;
}

というように定義されていて、これを実装すると T 型から U 型への変換ができるようになる。

以下、サンプルコード。Rust by Example と同じ。

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

// `i32` 型から `Number` 型へ変換できるようになる
impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Self { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);
}

Into トレイト

先ほどの

impl From<T> for U {
  fn from(T) -> Self;
}

を実装することによって、自動的に Into トレイトも実装されたことになり、

T型の値.into()

という書き方で U 型に変換できるようになる。

fn main() {
    // From トレイトを使うとこういう書き方だったが
    let num = Number::from(30);
    println!("My number is {:?}", num);

    // Into トレイトにより、こう書くこともできる
    // 変数の方の型を省略するとエラーになる
    let num: Number = 30.into();
    println!("My number is {:?}", num);

}

しばらく勘違いしてたのだが、 FromInto が対になってて T ←→ U 間で相互に変換可能になるわけではなく、変換の方向としてはどちらも同じ(サンプルでいうと、 i32Number)。

なお、上のサンプルでは変数の方に型を書いているが、メソッドチェーンなどでそれができないときは

let num = Into::<Number>::into(5);

のようにも書ける。

参考にさせていただいた記事:

From トレイトと Into トレイト、どちらを実装すべき?

std::convert::From - Rust

によれば

One should always prefer implementing From over Into because implementing From automatically provides one with an implementation of Into thanks to the blanket implementation in the standard library.

なので、基本は From トレイトを実装すると理解しておけばよさそう。

また、じゃあどういうときに Into トレイトの方を使うのか?についても直後に書かれており

Only implement Into when targeting a version prior to Rust 1.41 and converting to a type outside the current crate. From was not able to do these types of conversions in earlier versions because of Rust's orphaning rules. See Into for more details.

Rust のバージョンが 1.41 より前は、変換先の型が現在のクレートの外にある場合 From トレイトではコンパイルエラーになっていたらしい。

U::from(t)t.into() どちらで書くべき?

わからない。好みの問題な気がする。
Into トレイトの最後で触れたように変数の方に型を書けないケースとかを考えると U::from(t) の方が好み。

余談: TryFromTryInto

似たようなトレイトとして、TryFromTryInto というのもあるらしい。

TryFrom and TryInto - Rust By Example

これは、やっていることは同じようにある型から別の型への変換だが、失敗する可能性があるときはこちらを使う。

use std::convert::TryFrom;

#[derive(Debug, PartialEq)]
struct EvenNumber(i32);

impl TryFrom<i32> for EvenNumber {
    type Error = ();

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err(())
        }
    }
}

失敗する可能性があるので try_from() の戻り値は Result 型になっている。

リファレンス