본문 바로가기
학습 내용/Rust

[일반 프로그래밍 개념] 데이터 타입 - 2. 컴파운드 타입

by yein 2022. 5. 29.
  • 컴파운드 타입(compound type)은 여러 개의 값을 하나의 타입으로 그룹화한 타입
  • 러스트에선 두 가지의 컴파운드 타입을 지원
    • 튜플(tuples)
    • 배열(arrays)

 

변수의 타입 확인하기

  • 컴파운드 타입에 대해 알아보기 전에, 컴파운드 타입이 어떻게 이루어졌는지 잘 살펴보기 위해 변수의 타입을 출력하는 함수를 만들어 놓자. (학습용 및 디버깅 목적)
// main.rs

/** 변수의 타입을 출력하는 함수 */
fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>());
}

*참고: https://stackoverflow.com/questions/21747136/how-do-i-print-the-type-of-a-variable

 

튜플 타입

  • 고정된 길이를 가지며 크기를 변경할 수 없다.
  • 생성 방법
    • 소괄호 안에 각 값을 쉼표로 구분하여 표기 (타입 같지 않아도 됨.)
fn main() {
    let tup1 = (910, 33.3, 9);
    // 다음과 같이 type annotation을 적용하는 것도 가능
    let tup2: (i32, f64, u8) = (910, 33.3, 9);

    print_type_of(&tup1); // (i32, f64, i32)
    print_type_of(&tup2); // (i32, f64, u8)
}
  • 저번 시간에 공부했던 내용과 같이, 러스트에서 기본 정수 타입은 i32이기 때문에 정수의 타입을 따로 명시하지 않으면 컴파일러는 해당 정수의 타입을 i32로 추론한다. 따라서 원하는 타입이 있다면 tup2를 정의할 때와 같이 type annotation을 사용하면 된다.
  • 다음과 같이 튜플을 구조 분해하여 튜플의 개별 값을 읽을 수도 있다.
let tup2: (i32, f64, u8) = (910, 33.3, 9);

let (_, a, b) = tup2;
println!("a + b = {}", a + f64::from(b)); // a + b = 42.3
  • 또한 다음과 같이 마침표 다음에 원하는 요소의 인덱스를 사용하여 해당 요소를 직접 참조할 수도 있다. (보통의 경우와 마찬가지로 튜플의 첫 인덱스도 0부터 시작)
let nine1 = tup1.2;
let nine2 = tup2.2;
print_type_of(&nine1); // i32
print_type_of(&nine2); // u8
  • 근데 여기서 흥미로운 건... nine1nine2를 비교하는 문을 적는 순간, 타입을 명시하지 않았던 nine1의 타입이 기본 정수 타입인 i32에서 u8로 바뀐다는 것이다.
print_type_of(&nine1); // u8
print_type_of(&nine2); // u8

println!("nine1 == nine2 일까요? 정답은 {} 입니다.", nine1 == nine2);
// nine1 == nine2 일까요? 정답은 true 입니다.

 

배열 타입

  • 튜플과 달리 배열의 모든 요소는 동일한 타입이어야 한다.
  • 튜플과 마찬가지로 배열도 고정된 길이를 가진다.
  • 생성 방법
    • 대괄호 안에 각 값을 쉼표로 구분하여 나열 (타입 모두 동일해야 함.)
let arr = ["토끼", "다람쥐", "참새"];
print_type_of(&arr); // [&str; 3]
  • 배열은 데이터를 heap 메모리가 아닌 stack 메모리에 할당하거나, 고정된 개수의 요소를 다룰 때 유용하다고 한다.
    • 배열은 stack에 할당된 한 덩어리의 메모리
  • cf.) 벡터(Vector)
    • 표준 라이브러리가 지원함.
    • 배열과 유사하나 더 유연함. (크기를 자유롭게 조정할 수 있음.)
    • 보통은 벡터를 사용하는 편이 더 유용하긴 하나, 길이가 늘 고정되어 있는 데이터를 다루고자 할 때는 배열이 더 적합함. (예: 일 년에 포함된 월의 이름)
// type annotation을 사용하고자 할 때는 다음과 같이 표기하면 된다.
let months: [&str; 12] = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
];
println!("일 년에는 몇 개의 월이 존재하나요?: {}개", months.len());
// 일 년에는 몇 개의 월이 존재하나요?: 12개
  • 배열 초기화하는 방법 (모두 동일한 값으로) : let 변수명 = [요소; 길이];
// 배열 초기화하기
let many_cats = ['🐱'; 20];
print_type_of(&many_cats); // [char; 20]
println!("{}마리의 고양이", many_cats.len()); // 20마리의 고양이
  • 배열에 접근하는 방법 : 배열[인덱스]
let cat = many_cats[0];
println!("고양이: {}", cat); // 고양이: 🐱

// 튜플처럼 마침표 뒤에 인덱스를 표기하는 방식은 배열에 대해선 사용할 수 없다.
// let cat = many_cats.0;
// ⛔️instead of using tuple indexing, use array indexing: `many_cats[0]`
  • 배열 범위를 벗어난 인덱스의 값을 읽으려고 하면, 컴파일 시점이 아닌 런타임에 에러가 발생한다!
    • 이렇게 범위 밖의 인덱스 값을 읽으려는 시도에 에러를 발생시키는 것은 러스트의 안전성 원리(memory safety principles)와 관련 있다. 보통 저수준 언어에선 이러한 시도에 엉뚱한 값을 읽어오기도 하지만 러스트는 프로그램을 중단시켜 그러한 위험을 방지한다.
let no_cat = many_cats[20];
println!("고양이...?: {}", no_cat);
// ⛔️런타임 에러! 🙅🏻‍♀️
// thread 'main' panicked at 'index out of bounds: the len is 20 but the index is 20', src/main.rs:61:18
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

 

 


 

참고

https://rinthel.github.io/rust-lang-book-ko/ch03-00-common-programming-concepts.html

 

보편적인 프로그래밍 개념 - The Rust Programming Language

이번 챕터에서는 모든 프로그래밍 언어가 대부분 가진 개념이 Rust에서는 어떻게 다루어지는지 알아보고자 합니다. 많은 프로그래밍 언어가 보편적인 핵심요소를 갖습니다. 이번 챕터에서 Rust

rinthel.github.io