
왜 Rust는 변수를 바꾸는 데 까다로울까요?
파이썬이나 자바스크립트 같은 언어를 쓰다가 Rust를 처음 접하면 가장 먼저 당황스러운 순간이 찾아와요. 분명히 값을 바꿨는데 컴파일러가 “cannot assign twice to immutable variable”이라는 차가운 메시지를 던지며 실행을 막아버리거든요. 분명히 변수에 새로운 값을 넣었을 뿐인데, Rust는 마치 큰 잘못이라도 저지른 것처럼 엄격하게 반응해요.
이런 경험을 한 번이라도 해봤다면 여러분은 이미 Rust의 철학에 발을 들인 셈이에요. 다른 언어들은 변수의 값을 언제든 자유롭게 바꿀 수 있도록 허용하지만, Rust는 기본적으로 모든 변수를 불변(Immutable) 상태로 고정해버려요. 처음에는 이 규칙이 개발의 흐름을 끊는 방해물처럼 느껴질 수 있지만, 사실 이것은 여러분의 코드가 예상치 못한 곳에서 변질되는 것을 막아주는 아주 강력한 방어막이에요.
프로그램이 커지다 보면 어디서 값이 바뀌었는지 추적하기가 매우 힘들어지는데, Rust는 애초에 값이 바뀔 수 있는 통로를 아주 좁게 설계해서 이 문제를 원천 차단해요. 이 글을 끝까지 읽고 나면, 단순히 에러를 피하는 법을 넘어 Rust가 왜 이런 방식을 선택했는지, 그리고 실무에서 가변성을 어떻게 효율적으로 다루어야 하는지 명확히 알게 될 거예요.
이번 글에서는 다음과 같은 내용들을 차근차근 다뤄볼게요.
- 변수의 기본 선언 방식과 불변성의 의미
- 가변성을 부여하는 mut 키워드 활용법
- 섀도잉(Shadowing)을 이용한 똑똑한 변수 재선언
- 상수(Constant)와 변수의 결정적인 차이점
Rust 변수 사용 전 꼭 알아야 할 기본 개념
본격적으로 코드를 작성하기 전에, Rust가 변수를 바라보는 관점을 먼저 이해해야 해요. Rust에서 변수는 단순히 데이터를 담는 상자가 아니라, 데이터의 소유권과 상태를 정의하는 약속이에요. 이 약속을 어기면 컴파일러는 절대 코드를 실행해주지 않아요.
가장 먼저 기억해야 할 점은 Rust의 기본값이 불변성이라는 사실이에요. 어떤 값을 변수에 할당했다면, 그 값은 그 자리에서 멈춰 있어요. 만약 값을 바꾸고 싶다면 반드시 개발자가 직접 “이 변수는 바뀔 수 있어요”라고 명시해야 해요. 이런 설계 덕분에 우리는 멀티스레드 환경에서도 데이터가 갑자기 바뀌어 발생하는 버그를 훨씬 적게 경험할 수 있어요.
Rust에서 변수를 선언할 때 사용하는
let 키워드는 해당 변수의 생명주기와 범위를 결정하는 시작점이에요. 이 키워드 뒤에 붙는 추가적인 문구들이 변수의 성격을 결정하게 돼요.상황에 따라 어떤 방식을 선택해야 할지 고민될 때가 많을 거예요. 아래 표를 통해 불변 변수와 가변 변수의 차이를 한눈에 비교해 보세요.
| 구분 항목 | 불변 변수 (let) | 가변 변수 (let mut) |
|---|---|---|
| 기본 설정 | 기본값 (Default) | 명시적 선언 필요 |
| 값 변경 가능 여부 | 불가능 (Read-only) | 가능 (Read/Write) |
| 안전성 수준 | 매우 높음 | 상대적으로 주의 필요 |
| 주요 용도 | 설정값, 고정 데이터 | 카운터, 누적 합계 등 |
단순히 “바꿀 수 있다, 없다”의 차이를 넘어, 코드를 읽는 다른 개발자에게 “이 값은 프로그램이 끝날 때까지 변하지 않으니 안심하고 읽으세요”라는 신호를 주는 것이 불변성의 진짜 목적이에요. 이 개념을 머릿속에 넣고 다음 단계로 넘어가면 훨씬 이해가 빨라질 거예요.
단계별로 익히는 Rust 변수와 가변성 활용법
이제 실전 코드를 통해 구체적인 사용법을 익혀볼 시간이에요. 이론으로만 듣던 내용을 직접 코드로 구현해보면 Rust의 엄격함이 왜 유용한지 몸소 느낄 수 있어요.
STEP 1. 기본 불변 변수 선언하기
가장 먼저 익혀야 할 것은 아무런 수식어 없이 변수를 만드는 방법이에요. Rust에서는 let 키워드만 사용하면 자동으로 불변 변수가 생성돼요.
예를 들어, 사용자의 이름을 저장하는 변수를 만든다고 가정해볼게요. 이름은 한 번 정해지면 프로그램 실행 도중에 바뀔 일이 거의 없죠. 이럴 때 바로 불변 변수를 사용해요.
let name = "Alice"; 라고 선언하면, 이후에 name = "Bob";이라고 쓰는 순간 컴파일러가 즉시 에러를 발생시켜요. 이는 실수로 이름을 바꾸는 사고를 미연에 방지해줘요.이 방식은 코드의 예측 가능성을 극대화해요. 함수에 변수를 전달할 때도 이 값이 변하지 않는다는 확신이 있으니 훨씬 안전하게 로직을 짤 수 있어요.
STEP 2. mut 키워드로 가변성 제어하기
하지만 세상 모든 데이터가 고정되어 있지는 않죠. 게임의 점수나 현재 시간처럼 계속해서 변해야 하는 값들이 있어요. 이때 필요한 것이 바로 mut 키워드예요.
변수 이름 앞에 mut를 붙여주기만 하면, 그 변수는 이제 자유롭게 값을 갈아치울 수 있는 상태가 돼요. 아래는 점수를 관리하는 간단한 시나리오예요.
먼저 점수를 0으로 시작해요. 그리고 적을 물리칠 때마다 점수를 올려야 하죠. 이 과정은 반드시 가변 변수여야만 가능해요.
let mut score = 0;score = score + 10;score += 5;
이렇게 mut를 명시함으로써, 우리는 코드의 다른 부분에서 “아, score라는 변수는 프로그램 흐름에 따라 값이 변할 수 있는 변수구나”라고 즉시 인지할 수 있어요. 가변성을 최소한으로, 꼭 필요한 곳에만 사용하는 습관을 들이는 것이 중요해요.
STEP 3. 섀도잉(Shadowing)을 활용한 변수 변환
가끔은 값을 바꾸고 싶긴 한데, 데이터의 타입까지 바꿔야 하는 까다로운 상황이 생겨요. 예를 들어, 사용자가 입력한 문자열 “42”를 실제 숫자 42로 바꾸고 싶을 때가 있죠. 이때 mut를 쓰면 타입이 맞지 않아 에러가 나요. 여기서 등장하는 마법 같은 기능이 바로 섀도잉이에요.
섀도잉은 기존에 선언된 변수와 같은 이름을 가진 새로운 변수를 다시 선언하는 기법이에요. 기존 변수를 가리는(Shadowing) 것이죠. let 키워드를 다시 사용하기 때문에, 타입이 달라도 아무 문제 없어요.
let spaces = " "; // 문자열 타입
let spaces = spaces.len(); // 숫자 타입으로 변신!
이 방식의 장점은 변수의 이름을 계속 유지하면서도, 데이터의 형태를 깔끔하게 변환할 수 있다는 점이에요. mut를 사용해 값을 하나씩 수정해 나가는 것보다 훨씬 안전하고 코드가 간결해져요. 다만, 너무 남용하면 코드를 읽는 사람이 혼란을 느낄 수 있으니 주의해야 해요.
STEP 4. 상수(const)와 변수의 차이 이해하기
가변성과 불변성을 공부하다 보면 “그럼 상수는 뭐가 달라요?”라는 질문이 자연스럽게 나와요. 상수 역시 변하지 않지만, 변수와는 근본적으로 다른 특징을 가지고 있어요.
상수는 선언할 때 반드시 타입을 명시해야 하고, 컴파일 시점에 값이 결정되어야 해요. 또한, 상수는 let이 아닌 const 키워드를 사용하며, 프로그램 어디에서나 사용할 수 있도록 설계되는 경우가 많아요.
const MAX_POINTS: u32 = 100; // 상수는 타입을 꼭 써줘야 해요!
변수는 프로그램이 실행되는 도중에 계산되어 결정될 수 있지만, 상수는 태생부터 결정된 고정값이에요. 따라서 프로그램 전체에서 변하지 않는 수학적 상수나 환경 설정값에는 상수를 쓰는 것이 가장 바람직해요.
실전 시나리오: 간단한 카운터 프로그램
위의 개념들을 종합해서 작은 프로그램을 만들어볼게요. 이 흐름을 따라가며 각 단계의 역할을 확인해 보세요.
- 환경 설정값(상수) 정의:
const LIMIT: i32 = 10; - 초기값 선언(불변):
let initial_name = "Player"; - 진행 상태 관리(가변):
let mut current_score = 0; - 변환 작업(섀도잉):
let input = "5";
let input: i32 = input.parse().unwrap();
이처럼 상황에 맞는 적절한 도구를 선택하는 것이 바로 숙련된 Rust 개발자로 가는 길이에요.
자주 하는 실수와 해결법 및 FAQ
자주 하는 실수와 해결법
Rust를 배우다 보면 누구나 한 번쯤 마주치는 실수들이 있어요. 미리 알고 있으면 당황하지 않고 해결할 수 있겠죠?
❌ 실수: 불변 변수에 새로운 값을 할당하려고 할 때
왜 발생하는가: Rust의 기본값은 불변성이기 때문에, let으로 만든 변수는 수정할 수 없어요.
✅ 해결법: 값을 변경해야 하는 변수라면 선언할 때 let mut를 사용하여 가변성을 명시해 주세요.
❌ 실수: 섀도잉과 가변성을 혼동할 때
왜 발생하는가: 타입을 바꾸고 싶을 때 mut를 쓰려고 하면 타입 불일치 에러가 나요.
✅ 해결법: 데이터의 타입 자체를 변환해야 한다면 let을 다시 써서 섀도잉을 활용하세요.
❌ 실수: 상수에 런타임 계산 값을 넣으려고 할 때
왜 발생하는가: 상수는 컴파일 시점에 값이 확정되어야 하기 때문이에요.
✅ 해결법: 실행 중에 결정되는 값은 const가 아닌 일반 let 변수를 사용해야 해요.
❌ 실수: 너무 많은 변수를 가변(mut)으로 만들 때
왜 발생하는가: 가변 변수가 많아질수록 코드의 어느 부분에서 값이 바뀌었는지 추적하기 어려워져요.
✅ 해결법: 기본적으로 불변으로 선언하고, 정말 값이 변해야 하는 순간에만 mut를 추가하는 습관을 가지세요.
자주 묻는 질문
Q. 섀도잉을 쓰면 메모리를 계속 새로 사용하나요?
기존 변수와 이름이 같더라도 섀도잉은 새로운 변수를 생성하는 과정이에요. 하지만 Rust의 최적화 기능 덕분에 실제 실행 시에는 메모리 효율성을 최대한 유지하며 처리되니 걱정하지 않으셔도 돼요.
Q. 가변 변수를 쓰면 프로그램이 느려지나요?
가변성을 부여한다고 해서 성능이 눈에 띄게 저하되지는 않아요. 다만, 코드의 안전성을 검증하는 컴파일러의 부담이 약간 늘어날 수는 있지만 체감될 정도는 아니에요.
Q. 상수는 왜 타입을 반드시 써줘야 하나요?
상수는 컴파일 단계에서 매우 강력하게 처리되는데, 이때 타입이 명확해야 컴파일러가 혼동 없이 최적화된 기계어를 생성할 수 있기 때문이에요.
Q. let mut를 쓰면 모든 값이 안전하지 않은가요?
그렇지 않아요. 가변성을 쓰더라도 Rust의 소유권(Ownership) 규칙이 여전히 작동하기 때문에, 메모리 오염이나 잘못된 접근은 여전히 막아줘요.