Rustの型変換の実装について、 この記事では、From, TryFrom, ToString, Display, FromStr トレイトの実装方法と、使い分けについて紹介します。
From, Into
Fromトレイトを実装するとIntoトレイトも自動で実装されます。
実装された型は、from, into メソッドで型変換ができるようになります。
From実装のサンプル
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
// (i32, i32)からPointに変換する実装
impl From<(i32, i32)> for Point {
fn from((x, y): (i32, i32)) -> Self {
Point { x, y }
}
}
From実装の使用例
#[test]
fn test_from() {
let p = Point::from((1, 2));
assert_eq!(p, Point { x: 1, y: 2 });
}
#[test]
fn test_into() {
let p: Point = (1, 2).into();
assert_eq!(p, Point { x: 1, y: 2 });
}
Fromトレイトを実装すると、Intoトレイトも自動で実装されています。
intoメソッドを使う際には、型推論ができる状態か、宣言に型を明記しておきましょう。
TryFrom, TryInto
FromとIntoトレイトと役割は同じですが、Result で返せるので、 型変換できなかった場合を考慮する必要があるときは、こちらを使います。
TryFromトレイトを実装するとTryIntoトレイトも自動で実装されます。
try_from, try_into メソッドで変換ができるようになります。
TryFrom実装のサンプル
#[derive(Debug, PartialEq)]
enum MyEnum {
A,
B,
C,
}
// 変換エラー時のError型
#[derive(Debug, PartialEq)]
struct MyEnumIllegalValue(String);
// i32からMyEnumに変換する実装
impl TryFrom<i32> for MyEnum {
type Error = MyEnumIllegalValue;
fn try_from(value: i32) -> Result<Self, Self::Error> {
use MyEnum::*;
match value {
0 => Ok(A),
1 => Ok(B),
2 => Ok(C),
_ => Err(MyEnumIllegalValue(format!("Illegal value: {}", value))),
}
}
}
MyEnumIllegalValue型は、変換エラー発生時に使う型で、TryFromの関連型として定義しています。
もし、詳細なエラー情報が不要になる場合は、エラー生成にコストがかかるのでなしにしても良いと思います。
(関連型を type Error = ();
とするなど)
TryFrom実装の使用例
#[test]
fn test_try_from() {
assert_eq!(MyEnum::try_from(0), Ok(MyEnum::A));
assert_eq!(MyEnum::try_from(1), Ok(MyEnum::B));
assert_eq!(MyEnum::try_from(2), Ok(MyEnum::C));
assert_eq!(
MyEnum::try_from(3),
Err(MyEnumIllegalValue("Illegal value: 3".to_string()))
);
}
#[test]
fn test_try_into() {
assert_eq!(0.try_into(), Ok(MyEnum::A));
assert_eq!(1.try_into(), Ok(MyEnum::B));
assert_eq!(2.try_into(), Ok(MyEnum::C));
// Error型と比較している為、目的の型を明示するためにUFCS形式で書いています。
// ?演算子を使う場合は、型推論が働くので、UFCS形式は不要です。
assert_eq!(
<i32 as TryInto<MyEnum>>::try_into(4),
Err(MyEnumIllegalValue("Illegal value: 4".to_string()))
);
}
ToString, fmt::Display
特定の型を単純に文字列化したければ、ToStringを実装すればよいですが、
fmt::Displayトレイトで実装しておくと、ToStringも自動的に実装されます。
しかも、こちらの方が柔軟性が高いです。
use std::fmt;
struct Person {
name: String,
age: u32,
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", self.name, self.age)
}
}
#[test]
fn test_display() {
let person = Person {
name: "John".to_string(),
age: 42,
};
assert_eq!(person.to_string(), "John(42)");
}
ToStringも自動で実装されているのがわかります。
FromStr
文字列から、parse関数を使って特定の型に変換したい場合には、FromStrトレイトを使うのが直観的です。
FromStr実装のサンプル
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct Person {
name: String,
age: u32,
}
/// 変換失敗時のエラー型
#[derive(Debug, PartialEq)]
struct PersonParseError(String);
// 文字列から変換する実装
impl FromStr for Person {
type Err = PersonParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use nom::bytes::complete::tag;
use nom::character::complete::{alpha1, u32};
use nom::sequence::delimited;
use nom::IResult;
/// nomを使って、"name(age)" 形式の文字列をパースする。
fn parse_person(input: &str) -> IResult<&str, Person> {
let (input, name) = map(alpha1, |s: &str| s.to_owned())(input)?;
let (input, age) = delimited(tag("("), u32, tag(")"))(input)?;
Ok((input, Person { name, age }))
}
// 変換に失敗した場合はPersonParseErrorを返す。
parse_person(s)
.map(|(_, p)| p)
.map_err(|_| PersonParseError(s.to_string()))
}
}
FromStr実装の使用例
#[test]
fn test_from_str() {
// 文字列からPersonに変換できること。
let p: Person = "John(42)".parse().unwrap();
assert_eq!(p.name, "John");
assert_eq!(p.age, 42);
// 失敗した場合、PersonParseErrorが返ること。
let p: Result<Person, _> = "illegal word".parse();
assert_eq!(p, Err(PersonParseError("illegal word".to_string())));
}
まとめ
型変換したい場合、どのトレイトを実装しておくのが良いか?
- 文字列から特定の型に変換したい場合、FromStrを使う。
- 特定の型を文字列に変換したい場合、Displayを使う。
- 文字列以外の型を、特定の型に変換したい場合、TryFromを使う。
- 文字列以外の型を、特定の型を変換したい場合、失敗しないのであれば Fromを使う。