先日こんな記事を書いたのだが、最後に登場した 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); }
しばらく勘違いしてたのだが、 From
と Into
が対になってて T
←→ U
間で相互に変換可能になるわけではなく、変換の方向としてはどちらも同じ(サンプルでいうと、 i32
→ Number
)。
なお、上のサンプルでは変数の方に型を書いているが、メソッドチェーンなどでそれができないときは
let num = Into::<Number>::into(5);
のようにも書ける。
参考にさせていただいた記事:
From
トレイトと Into
トレイト、どちらを実装すべき?
によれば
One should always prefer implementing
From
overInto
because implementingFrom
automatically provides one with an implementation ofInto
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. SeeInto
for more details.
Rust のバージョンが 1.41 より前は、変換先の型が現在のクレートの外にある場合 From
トレイトではコンパイルエラーになっていたらしい。
U::from(t)
と t.into()
どちらで書くべき?
わからない。好みの問題な気がする。
Into
トレイトの最後で触れたように変数の方に型を書けないケースとかを考えると U::from(t)
の方が好み。
余談: TryFrom
と TryInto
似たようなトレイトとして、TryFrom
と TryInto
というのもあるらしい。
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
型になっている。