From, TryFrom (Feat. Into, TryInto)
From 트레잇을 사용하면, 다른 타입을 대상 타입으로 변환할 수 있습니다.
이미 많이 사용해왔던 기능인데, String::from("foo") 등으로 사용해왔던 트레잇입니다:
#![allow(unused)] fn main() { use std::convert::*; #[derive(Debug, PartialEq)] struct Foo { bar: usize, } impl From<usize> for Foo { fn from(item: usize) -> Self { Foo { bar: item } } } let x = Foo::from(5); assert_eq!(x, Foo { bar: 5 }); }
하지만 Box<T>같은 타입은 new 메서드를 사용해서 Box를 생성할 수 있습니다. (물론 From 트레잇도 구현되어 있으나, 둘 모두 같은 기능을 합니다.)
그럼 왜 굳이 From 트레잇을 구현하는가 하면, 매우 간단명료하게 대답할 수 있습니다:
Box의 new와 from은 제네릭 T를 받고, from은 new를 호출한합니다.
(제네릭 T를 받지 않는 타입 (ex; String) 같은 경우, new 보단 from이 더욱 편합니다.)
그러므로 new와 from 메서드의 기능은 똑같지만, 후술할 Into 덕분에 from이 존재합니다.
Into
Into는 From을 구현하면 자동으로 구현됩니다. 다만, 사용 방법이 조금 다릅니다:
#![allow(unused)] fn main() { use std::convert::*; #[derive(Debug, PartialEq)] struct Foo { bar: usize, } impl From<usize> for Foo { fn from(item: usize) -> Self { Foo { bar: item } } } let x = Foo::from(5); assert_eq!(x, Foo { bar: 5 }); let x: Foo = 5usize.into(); assert_eq!(x, Foo { bar: 5 }); }
into를 사용할 땐, 컴파일러가 어떤 타입인지 모르기 때문에, 타입 어노테이션을 명시해주어야 합니다.
TryFrom, TryInto
관련 자료 등을 찾아보면 TryFrom과 TryInto가 존재하는 것을 알 수 있는데, 이들은 이름 그대로 반환되는 값이 Result<T, E>의 경우일 때 사용합니다.
대표적으로, 타입 변환을 사용할 때 이용됩니다:
#![allow(unused)] fn main() { let x: Result<i64, _> = 5i32.try_into(); }
타입으로 쓰인 _는 반환 값을 무시하는 _ = expr; 문법이 아닌, infer 타입을 뜻합니다.
TryFrom 또한, From 트레잇과 비슷하게 구현할 수 있습니다:
#![allow(unused)] fn main() { use std::convert::*; #[derive(Debug, PartialEq)] struct Foo { bar: usize, } impl TryFrom<usize> for Foo { type Error = &'static str; fn try_from(value: usize) -> Result<Self, Self::Error> { if value != 0 { Ok(Foo { bar: value }) } else { Err("Error") } } } let x = Foo::try_from(5); assert_eq!(x, Ok(Foo { bar: 5 })); let x = Foo::try_from(0); assert_eq!(x, Err("Error")); let _: Foo = 5usize.try_into().unwrap(); }
다만 차이점은, Error라는 연관 타입(associated type)이 존재하는데, 그냥 Result<T, E>의 제네릭 E 입니다.