Logo

Rust 기초: From과 Into 트레잇

자료형 간의 명시적이고 안전한 데이터 변환은 Rust의 중요한 철학 중 하나입니다. Rust는 FromInto라는 표준 트레잇을 제공하여 데이터 변환을 안전하고 명확하게 할 수 있도록 돕는데요.

이 글에서는 이 두 트레잇의 관계와 차이점, 그리고 활용법을 살펴보겠습니다.

From 트레잇이란?

From 트레잇은 다른 자료형부터(from) 현재 자료형으로 변환하는 방법을 정의할 때 사용합니다. From 트레잇의 from() 메서드는 다른 제네릭(generic) 타입을 인자로 받고 자신의 타입을 반환합니다.

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

예를 들어, Rust에 내장된 String 자료형에는 &str 자료형으로 부터 변환되는 방법이 정의되어 있습니다.

impl From<&str> for String {
    fn from(s: &str) -> Self {
        String::new() + s
    }
}

String::from() 메서드를 덕분에 우리는 &str 자료형을 String 자료형으로 편하게 바꿀 수 있는 것이지요.

let str = String::from("Rust");

Into 트레잇이란?

Into 트레잇은 From 트레잇과 정반대의 반대 관점에서 데이터 변환 방법을 정의합니다. 즉, 현재 자료형이 다른 자료형으로(into)로 변환되는 방법을 정의할 수 있습니다.

Into 트레잇의 into() 메서드는 자신의 타입을 인자로 받고 다른 제네릭(generic) 타입을 반환합니다,

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

From 트레잇을 구현하면 컴파일러가 Into 트레잇은 자동으로 구현해줍니다. 따라서 Into 트레잇을 직접 구현한 일은 거의 없습니다.

예를 들어, &str 자료형을 String 자료형으로 바꿀 때, into() 메서드를 사용할 수도 있습니다.

let str: String = "Rust".into();

From 트레잇 구현

구조체열거형을 상대로 From 트레잇을 구현해보겠습니다.

예를 들어, xy 필드로 이루어진 Point 구조체를 정의하고, 튜플로부터 반환하는 방법을 From 트레잇으로 구현해보겠습니다.

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl From<(i32, i32)> for Point {
    fn from(coords: (i32, i32)) -> Self {
        let (x, y) = coords;
        Self { x, y }
    }
}

Point::from() 메서드로도 변환할 수 있고, 튜플.into() 메서드로도 변환할 수 있게 됩니다.

fn main() {
    let point1 = Point::from((1, 2));
    println!("{point1:?}");

    let point2: Point = (3, 4).into();
    println!("{point2:?}");
}
결과
Point { x: 1, y: 2 }
Point { x: 3, y: 4 }

TryFrom과 TryInto 트레잇

FromInto 트레잇은 데이터 변환이 항상 성공하는 상황에서 사용합니다. 데이터 변환이 실패할 가능성이 있다면 TryFromTryInto 트레잇을 사용해야합니다.

표준 라이브러리에 두 트레잇은 다음과 같이 정의되어 있습니다. FromInto 트레잇과 다르게 반환 자료형이 Result 임을 알 수 있습니다. 이를 통해서 데이터 변환이 실패햇을 때 발생할 수 있는 오류를 명시해줄 수 있습니다.

pub trait TryFrom<T>: Sized {
    type Error;

    fn try_from(value: T) -> Result<Self, Self::Error>;
}

pub trait TryInto<T>: Sized {
    type Error;

    fn try_into(self) -> Result<T, Self::Error>;
}

TryFrom 구현

TryInto 트레잇도 TryFrom 트레잇만 구현하면 컴파일러가 자동으로 구현해줍니다.

이번엔느 필드 값으로 양의 정수만 허용하는 PositivePoint 구조체를 정의하고, TryInto 트레잇을 구현하여 음수가 포함된 튜플이 인자로 들어오면 오류를 발생시키도록 구현해보겠습니다.

#[derive(Debug)]
struct PositivePoint {
    x: u32,
    y: u32,
}

impl TryFrom<(i32, i32)> for PositivePoint {
    type Error = String;

    fn try_from(coords: (i32, i32)) -> Result<Self, Self::Error> {
        let (x, y) = coords;

        if x < 0 || y < 0 {
            Err(String::from("Negative coordinate"))
        } else {
            Ok(Self {
                x: x as u32,
                y: y as u32,
            })
        }
    }
}

마찬가지로 try_from() 메서드와 try_into() 메서드, 모두 사용해서 데이터 변환을 할 수 있습니다.

fn main() {
    let ok = PositivePoint::try_from((10, 20));
    let err: Result<PositivePoint, String> = (10, -5).try_into();

    println!("{:?}", ok);
    println!("{:?}", err);
}
결과
Ok(PositivePoint { x: 10, y: 20 })
Err("Negative coordinate")

FromStr

데이터 변환 중에서 특히 문자열로부터 어떤 자료형으로 변화하는 작업은 매우 빈번하죠? 그래서 Rust의 표준 라이브러리에서는 From 트레잇의 특별한 형태인 FromStr도 제공합니다.

FromStr 트레잇은 다음과 같이 정의되어 있습니다. TryFrom과 마찬가지로 Result를 반환하여 발생할 가능성이 있는 오류를 표시해주도록 되어 있습니다.

pub trait FromStr: Sized {
    type Err;

    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

FromStr 트레잇의 from_str() 메서드를 구현하면 컴파일러가 자동으로 그 반대 작업을 해주는 parse() 메서드도 구현을 해줍니다.

use std::str::FromStr;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl FromStr for Point {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.trim().split(',').collect();

        if parts.len() != 2 {
            return Err(String::from("Invalid format"));
        }

        let x = parts[0].parse::<i32>().map_err(|e| e.to_string())?;
        let y = parts[1].parse::<i32>().map_err(|e| e.to_string())?;

        Ok(Point { x, y })
    }
}

FromStr 트레잇은 Rust의 많은 내장 자료형에 대해서 이미 구현이 되어 있습니다. 위 코드에서 i32 타입인 parts[0]parts[1] 상대로 parse() 메서드를 호출할 수 있는 이유입니다.

fn main() {
    let good = Point::from_str("10,20");
    let bad1: Result<Point, String> = "abc,xyz".parse();
    let bad2: Result<Point, String> = "42".parse();

    println!("{:?}", good);
    println!("{:?}", bad1);
    println!("{:?}", bad2);
}
결과
Ok(Point { x: 10, y: 20 })
Err("invalid digit found in string")
Err("Invalid format")

마치며

지금까지 Rust에서 데이터 변환의 핵심 매커니즘인 FromInto 트레잇에 대해서 알아보았습니다. 또한 같이 알아두면 좋은 TryFrom, TryInto, FromStr 트레잇에 대해서도 살펴보았습니다. 데이터 변환을 하실 때 본 포스팅에서 다룬 트레잇을 적지적소에 잘 활용하실 수 있으셨으면 좋겠습니다.