[IT-방법] Rust 변수와 가변성 빠른 시작 – 입문 개발자를 위한 실무 핵심 정리

Rust 변수와 가변성 빠른 시작를 설명하는 대표 이미지

Rust 변수와 가변성, 왜 처음에 막힐까요?

다른 언어를 배우다가 Rust에 입문하면 가장 먼저 당황하는 순간이 찾아와요. 분명히 변수에 값을 넣었는데, 다음 줄에서 그 값을 바꾸려고 하면 컴파일러가 빨간 줄을 그으며 화를 내기 때문이에요. “불변(immutable) 변수에는 값을 다시 할당할 수 없습니다”라는 메시지를 마주하면, 초보 개발자들은 내가 무엇을 잘못했는지 몰라 막막함을 느껴요.

이런 문제는 단순히 문법을 몰라서 발생하는 것이 아니에요. Rust가 설계된 근본적인 철학, 즉 메모리 안전성과 데이터 무결성을 지키려는 규칙을 이해하지 못했기 때문에 생기는 현상이에요. Rust는 개발자가 실수로 데이터를 변경하여 발생할 수 있는 수많은 버그를 컴파일 단계에서 미리 차단하려고 해요. 이 규칙을 배우는 과정은 괴롭지만, 한 번 익히고 나면 훨씬 더 견고한 소프트웨어를 만들 수 있는 강력한 무기를 얻게 되는 셈이에요.

오늘 이 글에서는 Rust 프로그래밍의 가장 기초이면서도 가장 강력한 벽인 변수와 가변성 규칙을 완전히 정복해 보려고 해요. 단순히 문법을 외우는 것이 아니라, 왜 Rust가 이런 방식을 선택했는지 그 이유를 깊이 있게 다룰 거예요. 이 글을 끝까지 읽고 나면 여러분은 더 이상 컴파일러와 싸우지 않고, 컴파일러를 동료로 삼아 코드를 짤 수 있게 될 거예요.

이 글에서 함께 나눌 내용은 다음과 같아요.

  • Rust 변수의 기본 성질과 불변성의 의미
  • 가변성을 안전하게 사용하는 방법과 주의점
  • 변수 쉐도잉(Shadowing)을 활용한 효율적인 코드 작성법
  • 상수(Constant)와 변수의 명확한 차이점
  • 실전에서 자주 발생하는 실수와 해결책

본격적인 시작 전, 꼭 알아야 할 기초 지식

Rust의 변수 시스템을 이해하기 위해서는 먼저 몇 가지 개념적 준비가 필요해요. Rust는 기본적으로 불변성(Immutability)을 원칙으로 삼는 언어예요. 우리가 흔히 사용하는 Python이나 JavaScript에서는 변수의 값을 언제든 바꿀 수 있지만, Rust는 ‘기본적으로 바꿀 수 없다’는 전제에서 시작해요. 이는 매우 중요한 차이점이에요.

왜 굳이 이렇게 불편하게 만들었을까요? 그 이유는 코드의 예측 가능성 때문이에요. 어떤 값이 변하지 않는다는 확신이 있다면, 우리는 그 값을 사용하는 다른 코드들이 갑자기 변할까 봐 걱정할 필요가 없어요. 이는 대규모 프로젝트에서 데이터가 어디서 어떻게 바뀌었는지 추적하는 데 드는 엄청난 비용을 줄여줘요.

💡 알아두기
Rust의 컴파일러는 아주 꼼꼼한 선생님과 같아요. 변수가 가변인지 불변인지 아주 엄격하게 체크하며, 규칙을 어기면 실행조차 허용하지 않아요. 하지만 이 엄격함 덕분에 실행 중에 프로그램이 갑자기 죽는 현상을 획기적으로 줄일 수 있어요.

실습을 시작하기 전에, 여러분이 어떤 방식으로 변수를 관리할지 결정할 수 있도록 기준을 정리해 보았어요. 상황에 맞는 선택 기준을 확인해 보세요.

구분 방식 권장 상황 주요 특징 안전성 수준
불변 변수 (let) 값이 변하지 않는 설정값, 고정 데이터 기본값, 값 변경 시 컴파일 에러 매우 높음
가변 변수 (let mut) 카운터, 상태 변화가 잦은 데이터 명시적 선언 필요, 값 변경 가능 보통
상수 (const) 프로그램 전체에서 쓰이는 절대 상수 컴파일 타임에 결정, 항상 불변 최상

위의 표를 보면 알 수 있듯이, Rust에서는 무조건 가변 변수를 쓰는 것이 능사가 아니에요. 오히려 가능한 한 불변 변수를 사용하고, 정말로 값이 바뀌어야 하는 순간에만 가변성을 부여하는 것이 Rust다운 코드를 작성하는 비결이에요. 이제 준비가 되었다면, 실제 코드로 어떻게 구현하는지 하나씩 살펴볼까요?

단계별 실전 실습: 변수와 가변성 마스터하기

이제 직접 코드를 작성하며 Rust의 변수 시스템을 몸소 느껴볼 시간이에요. 단순히 눈으로 보는 것보다 직접 타이핑하고 에러를 마주하는 것이 훨씬 빠르게 배우는 길이에요.

STEP 1. 불변 변수의 기본 선언과 특징

Rust에서 가장 먼저 배우는 것은 let 키워드를 사용한 변수 선언이에요. 특별한 명령어를 붙이지 않으면 모든 변수는 불변 상태로 태어나요.

let x = 5;
// x = 6; // 이 줄을 실행하면 컴파일 에러가 발생해요!

위의 예시에서 x라는 이름의 공간을 만들고 5라는 값을 담았어요. 하지만 만약 x = 6이라고 쓰면, 컴파일러는 즉시 작업을 중단하고 여러분에게 경고를 보내요. 이 방식은 프로그래밍 도중에 실수로 중요한 데이터가 바뀌어 버리는 사고를 원천 봉쇄해요. 예를 들어, 사용자의 고유 식별 번호(ID)나 결제 금액 같은 데이터는 프로그램 실행 중에 바뀌면 큰일 나잖아요? Rust는 이런 상황을 기본적으로 보호해 주는 거예요.

STEP 2. 가변성(mut) 부여하기

물론 현실적인 프로그래밍에서는 값이 변해야 하는 상황이 반드시 존재해요. 게임의 점수, 반복문의 카운터, 현재 사용자의 위치 정보 등이 그렇죠. 이럴 때는 변수 이름 앞에 mut 키워드를 붙여주면 돼요.

let mut score = 0;
score = 10;
score = score + 5;
println!("현재 점수: {}", score); // 출력: 15

이렇게 let mut를 사용하면, 해당 변수는 이제 ‘가변적’인 상태가 되어 자유롭게 값을 바꿀 수 있어요. 하지만 여기서 주의할 점이 있어요. mut를 붙이는 순간, 이 변수는 언제 어디서든 값이 바뀔 수 있는 위험 요소를 가지게 된다는 점이에요. 따라서 mut는 정말 필요한 곳에만 신중하게 사용해야 해요. 코드가 길어질수록 어디서 값이 바뀌었는지 찾는 것이 힘들어질 수 있기 때문이에요.

STEP 3. 변수 쉐도잉(Shadowing) 활용하기

가변 변수(mut)와 비슷해 보이지만, 훨씬 더 세련된 방법이 하나 있어요. 바로 변수 쉐도잉이에요. 이는 같은 이름의 변수를 다시 선언하여 이전 변수를 가리는 기법이에요.

let spaces = "   "; // 문자열 타입
let spaces = spaces.len(); // 숫자 타입으로 변신!
println!("공백의 개수: {}", spaces);

위의 코드를 보면 spaces라는 이름이 처음에는 문자열이었지만, 두 번째 선언에서는 숫자로 완전히 바뀌어 버렸어요. 가변 변수(mut)와의 결정적인 차이점은 무엇일까요? 가변 변수는 데이터 타입(Type)을 바꿀 수 없어요. 5라는 숫자를 담은 가변 변수에 갑자기 “Hello”라는 문자열을 넣으려고 하면 에러가 나요. 하지만 쉐도잉은 새로운 변수를 만드는 과정이기 때문에 타입이 완전히 달라져도 괜찮아요. 이는 데이터를 가공할 때(예: 사용자 입력 문자열을 숫자로 변환할 때) 매우 유용하게 쓰여요.

STEP 4. 상수(const)와 변수의 차이점

변수와 비슷해 보이지만 전혀 다른 상수에 대해서도 알아야 해요. 상수는 const 키워드를 사용하며, 한 번 정해지면 프로그램이 끝날 때까지 절대, 무슨 일이 있어도 바꿀 수 없어요.

const MAX_POINTS: u32 = 100_000;
// MAX_POINTS = 200_000; // 절대 불가능해요!

상수는 변수와 비교했을 때 몇 가지 특징이 더 있어요. 첫째, 반드시 타입을 명시해야 해요. 둘째, 컴파일 타임에 값이 결정되어야 해요. 즉, 실행 중에 계산되는 값은 상수에 넣을 수 없어요. 셋째, 이름 관례로 모두 대문자를 사용하는 경우가 많아요. 상수는 프로그램 전체에서 변하지 않는 물리 법칙이나 설정값처럼, 아주 강력한 불변성을 보장해야 할 때 사용해요.

STEP 5. 실전 시나리오: 게임 캐릭터 상태 관리하기

지금까지 배운 내용을 종합해서 작은 시나리오를 만들어 볼게요. 캐릭터의 이름은 변하지 않지만, 체력은 변하고, 공격력은 쉐도잉을 통해 계산된 값을 사용한다고 가정해 봅시다.

fn main() {
    // 1. 이름은 절대 바뀌지 않음 (불변)
    let character_name = "Rust Warrior";

    // 2. 체력은 전투 중에 계속 변함 (가변)
    let mut health = 100;
    health = health - 20;

    // 3. 입력된 공격력 문자열을 숫자로 변환 (쉐도잉)
    let raw_power = "50";
    let power: i32 = raw_power.parse().expect("숫자가 아니에요!");

    // 4. 게임 설정값 (상수)
    const MAX_LEVEL: u32 = 99;

    println!("캐릭터: {}", character_name);
    println!("남은 체력: {}", health);
    println!("공격력: {}", power);
    println!("최고 레벨: {}", MAX_LEVEL);
}

이 코드를 통해 우리는 상황에 따라 let, let mut, 쉐도잉, const를 어떻게 적재적소에 배치하는지 확인할 수 있어요. 이렇게 짜인 코드는 읽기 쉽고, 무엇이 변하고 무엇이 변하지 않는지 한눈에 들어오기 때문에 매우 안전한 코드가 된답니다.

💡 알아두기
실무에서는 ‘최소 권한의 원칙’을 적용해야 해요. 즉, 변수에게는 변할 수 있는 권한을 최대한 적게 줄수록 좋아요. 처음에는 무조건 불변으로 만들고, 에러가 나면 그때 가변으로 바꾸는 습관을 들이는 것이 가장 좋은 전략이에요.

자주 하는 실수와 해결법

Rust를 처음 접하면 누구나 겪는 실수들이 있어요. 이 실수들만 피해도 여러분의 학습 속도는 두 배 이상 빨라질 거예요.

  • 불변 변수에 값을 다시 넣으려고 할 때
    왜 발생하나요? mut 키워드를 빼먹었기 때문이에요.
    해결법: 변수 선언 시 let mut로 선언했는지 확인하세요.
  • 가변 변수의 타입을 중간에 바꾸려고 할 때
    왜 발생하나요? mut는 값의 변경은 허용하지만, 타입의 변경은 허용하지 않아요.
    해결법: 타입까지 바꿔야 한다면 mut 대신 쉐도잉(새로운 let 선언)을 사용하세요.
  • 상수(const)를 사용하며 타입을 생략할 때
    왜 발생하나요? 상수는 컴파일 타임에 타입을 확정해야 하므로 타입 명시가 필수예요.
    해결법: const NAME: &str = "Rust";처럼 항상 타입을 적어주세요.
  • 무분별하게 모든 변수에 mut를 붙일 때
    왜 발생하나요? 이는 Rust의 안전성을 스스로 포기하는 행위예요.
    해결법: 값이 바뀌어야 하는 명확한 근거가 있을 때만 mut를 사용하세요.
  • 쉐도잉을 너무 남용하여 변수 이름을 헷갈리게 할 때
    왜 발생하나요? 같은 이름을 계속 쓰면 코드 흐름을 놓치기 쉬워요.
    해결법: 데이터의 의미가 완전히 달라진다면 새로운 이름을 사용하는 것도 방법이에요.

자주 묻는 질문

Q. Rust는 왜 기본값이 불변인가요?

데이터가 예기치 않게 변하는 것을 막아 프로그램의 안정성을 높이기 위해서예요. 값이 변하지 않는다는 보장은 멀티스레드 환경이나 복잡한 로직에서 버그를 찾는 시간을 획기적으로 줄여준답니다.

Q. 변수 쉐도잉과 가변 변수(mut)의 차이가 정확히 뭔가요?

가변 변수는 ‘이미 만들어진 상자 안의 내용물’만 바꾸는 것이고, 쉐도잉은 ‘기존 상자를 버리고 새로운 상자(심지어 모양이 다른)를 만드는 것’과 같아요. 타입 변환이 필요하다면 무조건 쉐도잉을 써야 해요.

Q. 상수는 변수보다 왜 더 안전한가요?
상수는 컴파일 타임에 이미 값이 결정되어 코드 곳곳에 박히기 때문이에요. 실행 중에 값을 계산하거나 메모리 주소를 할당하는 등의 복잡한 과정을 거치지 않아 오류가 끼어들 틈이 없어요.

Q. 쉐도잉을 쓰는 게 나쁜 습관인가요?

아니요, 오히려 권장되는 패턴이에요! 특히 문자열 입력을 숫자로 바꿀 때처럼, 중간 단계의 임시 변수를 많이 만들지 않고도 깔끔하게 코드를 유지할 수 있게 도와주거든요.

Q. 가변 변수를 쓰면 메모리 관리가 힘들어지나요?

가변 변수 자체가 메모리 관리를 어렵게 하지는 않지만, 데이터가 언제 변했는지 추적하기 어렵게 만들 수는 있어요. 그래서 Rust는 가변성을 명시적으로 드러내게 하여 개발자가 스스로 주의를 기울이게 만들어요.

빠른 요약과 다음 단계

오늘 배운 핵심 내용을 잊지 않도록 마지막으로 정리해 볼게요. 이 체크리스트만 기억해도 Rust의 변수 규칙은 마스터한 것이나 다름없어요.

✅ 핵심 요약

  • Rust 변수는 기본적으로 불변(Immutable)이에요.
  • 값을 바꾸려면 반드시 let mut를 사용해야 해요.
  • 타입까지 바꾸며 값을 재정의하려면 쉐도잉(Shadowing)을 활용하세요.
  • 절대 변하지 않는 값은 const로 선언하고 타입을 명시하세요.
  • 가변성은 꼭 필요한 곳에만 최소한으로 사용하는 것이 원칙이에요.

이제 기초를 뗐으니 다음 단계로 나아갈 차례예요. 변수를 어떻게 선언하고 바꾸는지 알았다면, 이제 그 변수들이 메모리에서 어떻게 살아 움직이는지를 알아야 해요.

🚀 지금 바로 해야 할 일

  • 컴파일러 에러 메시지를 두려워하지 말고 직접 let 변수를 수정해 보세요.
  • 쉐도잉을 이용해 문자열을 숫자로 바꾸는 코드를 직접 작성해 보세요.

📅 이번 주 학습 목표

  • Rust의 핵심 개념인 소유권(Ownership)에 대해 공부하기
  • 변수를 다른 함수로 넘겨줄 때 발생하는 빌려오기(Borrowing) 이해하기

딱 10분 투자해서 Rust 변수와 가변성을 익힌 여러분, 정말 대단해요! 이 기초가 탄탄해야 나중에 소유권이라는 큰 산을 넘을 수 있어요. 포기하지 말고 계속해서 코드를 작성해 보세요!

관련하여 더 깊이 있는 내용이 궁금하다면 Rust 입문자를 위한 학습 가이드Rust 소유권과 빌려오기 완벽 정리 글을 참고해 보세요.

댓글 남기기