[Rust 시리즈 3편] 변수, 불변성, 기본 자료형

English version

2편에서 환경을 준비했다면, 이제 변수와 자료형 규칙을 이해해야 본격적으로 코드를 작성할 수 있습니다. Rust는 기본적으로 모든 변수를 불변으로 취급하며, let 선언 시 mut를 명시해야만 값을 바꿀 수 있습니다. 3편에서는 불변성 감각을 잡고, 스칼라 타입과 튜플·배열 같은 복합 타입을 차근차근 익힙니다.

이번 글에서 새로 나오는 용어

  1. 불변성 (immutability): 변수가 생성된 뒤 값을 바꿀 수 없는 성질로, Rust에서는 기본 동작입니다.
  2. 스칼라 타입: 한 번에 하나의 값을 표현하는 타입(i32, f64, bool, char 등)을 가리킵니다.
  3. 튜플 (tuple): 서로 다른 타입의 값을 순서 있게 묶는 고정 길이 컬렉션입니다.
  4. 배열 (array): 같은 타입의 값을 고정 길이로 저장하는 컬렉션으로, 컴파일 타임에 길이가 정해져야 합니다.

핵심 개념

  • let으로 선언한 변수는 기본적으로 불변이며, let mut를 사용해야 값 변경이 가능합니다.
  • 같은 이름으로 let을 다시 쓰는 shadowing은 기존 변수를 수정하는 것이 아니라, 같은 이름의 새 변수를 다시 선언하는 방식입니다.
  • Rust는 타입 추론을 지원하지만, 복잡한 상황이나 명시성이 필요한 경우 : 타입 구문으로 타입을 지정합니다.
  • 스칼라 타입은 정수(i32, u64), 실수(f32, f64), 불리언(bool), 문자(char)가 중심입니다.
  • 튜플은 여러 값을 순서로 묶을 때 사용하며, 점(.) 인덱싱이나 패턴 분해로 접근합니다.
  • 배열은 길이가 고정된 동일 타입 컬렉션이며, 슬라이스(7편)로 확장하기 전 단계에서 감각을 쌓습니다.

코드로 따라하기

1. let과 mut

fn main() {
    let language = "Rust"; // 불변
    let mut version = 1;    // 가변

    println!("{} v{}", language, version);
    version += 1;
    println!("다음 버전: v{}", version);
}

language는 불변이라 값을 바꾸려 하면 컴파일 오류가 발생합니다. versionmut 덕에 증가시킬 수 있습니다.

2. 스칼라 타입 명시

fn main() {
    let signed: i32 = -42;
    let unsigned: u64 = 2026;
    let pi: f64 = 3.14159;
    let ready: bool = true;
    let grade: char = 'A';

    println!("signed = {signed}, unsigned = {unsigned}");
    println!("pi = {pi}, ready = {ready}, grade = {grade}");
}

Rust는 리터럴에 접미사를 붙여 타입을 지정할 수도 있습니다(42u8, 1.0f32). 자주 쓰는 기본 정수는 i32, 기본 실수는 f64입니다.

3. shadowing

fn main() {
    let steps = 5;
    let steps = steps + 1;

    println!("steps = {steps}");
}

여기서 두 번째 let steps = ...는 기존 변수를 수정한 것이 아니라, 같은 이름의 새 변수를 다시 선언한 것입니다. 그래서 shadowing은 mut와 다릅니다. mut는 같은 변수를 바꾸는 것이고, shadowing은 같은 이름으로 새 변수를 덮어쓰듯 다시 만드는 방식입니다.

4. 튜플

fn main() {
    let user: (&str, u32, bool) = ("Jisoo", 3, true);

    println!("이름: {}", user.0);
    println!("레벨: {}", user.1);
    println!("활성 여부: {}", user.2);

    let (name, level, active) = user; // 패턴 분해
    println!("{name} / {level} / {active}");
}

튜플은 길이가 고정되어 있고, 서로 다른 타입을 함께 묶을 수 있다는 점이 특징입니다. 처음에는 user.0, user.1처럼 점 인덱싱에 먼저 익숙해지고, 패턴 분해는 "이런 방식도 있다" 정도로 받아들이면 충분합니다.

5. 배열

fn main() {
    let scores = [95, 88, 76, 100];
    let zeros = [0; 5]; // 길이 5, 모든 요소 0

    println!("첫 점수: {}", scores[0]);
    println!("배열 길이: {}", scores.len());

    for score in scores {
        println!("점수: {score}");
    }
}

배열 길이는 타입 일부이기 때문에 let data: [u8; 3] = [1, 2, 3];처럼 명시할 수 있고, 길이가 다른 배열끼리는 타입이 달라집니다.

튜플과 배열의 차이는 아래처럼 정리하면 쉽습니다.

  • 튜플: 서로 다른 타입을 함께 묶을 수 있음
  • 배열: 같은 타입만 묶을 수 있음
  • 둘 다: 길이가 고정됨

왜 중요한가

  • Rust의 불변 기본값은 값이 예상치 못하게 바뀌는 버그를 줄이고, 가변 상태를 의도적으로 드러내도록 강제합니다.
  • 타입을 명시적으로 다루면 컴파일 오류를 읽기 쉬워지고, ownership과 borrowing을 학습할 때 변수의 생명주기를 추적하기 수월해집니다.
  • 튜플과 배열을 이해하면 구조체, 슬라이스, 반복자 등 더 복잡한 데이터 구조를 학습할 때 쉽게 연결할 수 있습니다.

CodeSandbox로 이어서 실습하기

아래 샌드박스는 CodeSandbox의 Rust starter입니다. 이번 글의 핵심 코드를 src/main.rs에 옮기고, cargo checkcargo run 결과를 나란히 보면서 컴파일 메시지와 실행 출력을 비교해 보세요.

Live Practice

Rust Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Rust startercargoterminal
  1. starter를 fork한 뒤 src/main.rs를 연다
  2. 본문 예제를 붙여 넣고 cargo check와 cargo run을 차례로 실행한다
  3. 타입, 값, 참조 흐름을 바꿔 컴파일 피드백과 출력 차이를 비교한다

Rust 실습은 브라우저 미리보기보다 터미널 피드백이 더 중요합니다. 여러 파일 구조나 추가 crate가 필요한 예제는 파일 배치를 조금 더 손봐야 할 수 있습니다.

실습

  1. cargo new data-playground --bin을 만든 뒤, main.rs에서 불변 변수에 값을 다시 대입해 보고 컴파일 오류 메시지를 캡처합니다.
  2. 정수 배열에서 최댓값을 찾는 간단한 루프를 작성해 보고, mut 없이도 합계를 구할 수 있는지 실험합니다(힌트: iter().sum::<i32>()는 아직 다루지 않으므로 직접 누적 변수를 써도 됩니다).
  3. 튜플을 반환하는 작은 함수를 작성해 (sum, average) 같은 계산 결과를 돌려보며 패턴 분해를 연습합니다.
  4. let shadowed = 5; let shadowed = shadowed + 1;처럼 같은 이름을 다시 선언(shadowing)해, 기존 변수를 수정한 것이 아니라 새 변수를 선언한 패턴임을 설명해 봅니다.

마무리

불변성, 스칼라 타입, 튜플, 배열은 Rust 문법의 기초 체온을 결정합니다. 이 감각이 잡혀 있어야 함수와 제어 흐름을 다룰 때 변수의 범위와 상태를 자연스럽게 추적할 수 있습니다. 4편에서는 fn으로 함수를 정의하고, if와 반복문으로 흐름을 제어하는 방법을 이어서 살펴봅니다.

💬 댓글

이 글에 대한 의견을 남겨주세요