변환과 캐스팅

C#과 Rust는 모두 컴파일할 때 타입을 정적으로 검사합니다. 변수를 선언한 뒤에는 해당 타입으로 암시적으로 변환할 수 없는 다른 타입의 값을 할당할 수 없습니다. C#의 여러 타입 변환 방식에는 Rust에서 대응하는 방법이 있습니다.

암시적 변환

C#과 Rust 모두 암시적 변환을 지원합니다. Rust에서는 이를 타입 강제 변환이라고 부릅니다. 다음 예제를 살펴보겠습니다.

int intNumber = 1;
long longNumber = intNumber;

Rust는 허용하는 타입 강제 변환의 범위를 훨씬 좁게 제한합니다.

let int_number: i32 = 1;
let long_number: i64 = int_number; // 오류: `i64`가 필요하지만 `i32`를 받았습니다.

하위 타입 관계를 이용한 올바른 암시적 변환 예제는 다음과 같습니다.

fn bar<'a>() {
    let s: &'static str = "hi";
    let t: &'a str = s;
}

관련 자료:

명시적 변환

정보를 잃을 수 있는 변환에는 C#에서 캐스팅 식을 사용한 명시적 변환이 필요합니다.

double a = 1.2;
int b = (int)a;

명시적 변환은 다운캐스팅 과정에서 OverflowException이나 InvalidCastException 같은 예외를 발생시키며 실행 중 실패할 수 있습니다.

Rust는 기본 타입 간 강제 변환 대신 as 키워드를 사용한 명시적 변환, 즉 캐스팅을 제공합니다. Rust의 캐스팅은 패닉을 일으키지 않습니다.

let int_number: i32 = 1;
let long_number: i64 = int_number as _;

사용자 정의 변환

.NET 타입에는 한 타입을 다른 타입으로 바꾸는 사용자 정의 변환 연산자를 둘 수 있습니다. System.IConvertible도 타입 간 변환에 사용합니다.

Rust 표준 라이브러리는 From 트레이트와 그 역방향인 Into 트레이트로 값 변환을 추상화합니다. 어떤 타입에 From을 구현하면 Into의 기본 구현도 자동으로 제공됩니다. Rust에서는 이를 _일괄 구현_이라고 부릅니다. 다음 예제는 두 가지 타입 변환을 보여 줍니다.

fn main() {
    let my_id = MyId("id".into()); // `String`의 `From<&str>` 구현 덕분에 `into()`가 자동으로 제공됩니다.
    println!("{}", String::from(my_id)); // `String`의 `From<MyId>` 구현을 사용합니다.
}

struct MyId(String);

impl From<MyId> for String {
    fn from(MyId(value): MyId) -> Self {
        value
    }
}

관련 자료: