타입

타입 (Types)

Rust 프로그램의 모든 변수, 항목, 값은 타입을 가져요. 값의 타입은 그 값을 담는 메모리의 해석과 값에 수행할 수 있는 연산을 정의해요. 이번 장에서는 Rust의 타입 체계를 전체적으로 훑어볼게요.

출처: Rust Reference

본문

모든 변수, 항목, 값은 타입을 가져요. 값의 타입은 그 값을 담고 있는 메모리의 해석과 그 값에 수행할 수 있는 연산을 정의해요.

내장(built-in) 타입은 비자명한 방식으로 언어에 긴밀하게 통합되어 있어서, 사용자 정의 타입으로는 흉내낼 수 없는 부분이 많아요. 사용자 정의 타입은 제한된 능력을 가져요.

Rust의 타입 목록은 다음과 같아요:

  • 불리언bool
  • 숫자 — 정수와 실수
  • char
  • str
  • Never 타입! — 값을 하나도 갖지 않는 타입
  • 튜플 (Tuple)
  • 배열 (Array)
  • 슬라이스 (Slice)
  • 구조체 (Struct)
  • 열거형 (Enum)
  • 공용체 (Union)
  • 함수 (Functions)
  • 클로저 (Closures)
  • 참조 (References)
  • 원시 포인터 (Raw pointers)
  • 함수 포인터 (Function pointers)
  • trait 객체 (Trait objects)
  • impl trait

타입 표현식 (Type expressions)

Type →      TypeNoBounds
    | ImplTraitType
    | TraitObjectType

TypeNoBounds →      ParenthesizedType
    | ImplTraitTypeOneBound
    | TraitObjectTypeOneBound
    | TypePath
    | TupleType
    | NeverType
    | RawPointerType
    | ReferenceType
    | ArrayType
    | SliceType
    | InferredType
    | QualifiedPathInType
    | BareFunctionType
    | MacroInvocation

Type 문법 규칙에서 정의된 타입 표현식은 타입을 가리키는 구문이에요. 다음을 가리킬 수 있어요:

  • 시퀀스 타입(튜플, 배열, 슬라이스).
  • 원시 타입(불리언, 숫자, char, str).
  • 항목으로의 경로(구조체, 열거형, 공용체, 타입 별칭, trait).
  • Self가 구현 중인 타입일 때의 Self 경로.
  • 제네릭 타입 매개변수.
  • 포인터 타입(참조, 원시 포인터, 함수 포인터).
  • 컴파일러가 타입을 결정하도록 요청하는 추론된 타입.
  • 모호성 해소에 쓰이는 괄호.
  • trait 타입: trait 객체와 impl trait.
  • never 타입.
  • 타입 표현식으로 확장되는 매크로.

괄호로 감싼 타입 (Parenthesized types)

ParenthesizedType → ( Type )

어떤 상황에서는 타입의 조합이 모호할 수 있어요. 그럴 때는 타입 주위에 괄호를 써서 모호성을 피해요. 예를 들어 참조 타입 안에서 타입 바운드의 + 연산자가 어디에 적용되는지 불분명할 때 괄호 사용이 필요해요. 이렇게 모호성 해소가 필요한 문법 규칙은 Type 대신 TypeNoBounds 규칙을 사용해요.

#![allow(unused)]
fn main() {
use std::any::Any;
type T<'a> = &'a (dyn Any + Send);
}

재귀 타입 (Recursive types)

명목적(nominal) 타입 — 즉 구조체, 열거형, 공용체 — 은 재귀적일 수 있어요. 즉 각 열거형 변형이나 구조체·공용체 필드가, 직접적으로든 간접적으로든 그것을 감싸는 열거형이나 구조체 타입 자신을 가리킬 수 있어요.

이런 재귀에는 제약이 있어요:

  • 재귀 타입은 재귀에 명목적 타입을 포함해야 해요. (단순한 타입 별칭이나 배열·튜플 같은 다른 구조적 타입이 아니라.) 그래서 type Rec = &'static [Rec]는 허용되지 않아요.
  • 재귀 타입의 크기는 유한해야 해요. 다시 말해 재귀 필드는 포인터 타입이어야 해요.

재귀 타입과 그 사용 예시예요:

#![allow(unused)]
fn main() {
enum List<T> {
    Nil,
    Cons(T, Box<List<T>>)
}

let a: List<i32> = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil))));
}

더 알아보기 (Learn more)

  • 각 타입의 상세 규칙은 The Rust Reference의 타입별 챕터(숫자, 튜플, 배열, 슬라이스, 함수, 참조 등)에서 다뤄요.
  • 재귀 타입과 DST의 관계는 동적 크기 타입 챕터를 함께 보면 좋아요.