[IT-정보] Rust 변수와 가변성 사례로 배우는 기초 – 실무 오류를 방지하는 안전한 코딩법

Rust 변수와 가변성 사례를 설명하는 대표 이미지

Rust 변수와 가변성, 왜 처음부터 괴롭힐까요?

분명히 어제까지 잘 돌아가던 코드였는데, 갑자기 빨간색 에러 메시지가 화면을 가득 채우면 정말 당황스러워요. 특히 다른 언어에서 넘어온 분들이라면 더욱 그렇겠지요. 변수에 값을 하나 바꿨을 뿐인데, 컴파일러가 마치 큰 잘못이라도 저지른 것처럼 cannot assign twice to immutable variable이라는 메시지를 던지며 실행조차 거부하거든요.

이런 경험, Rust를 처음 배우는 분들이라면 누구나 겪는 통과의례예요. 왜 Rust는 이렇게까지 까다롭게 구는 걸까요? 단순히 개발자를 괴롭히기 위해서일까요? 결코 그렇지 않아요. Rust의 이런 엄격함은 사실 우리가 나중에 겪을 수 있는 수많은 메모리 오류와 데이터 경쟁 문제를 미리 막아주는 아주 강력한 방어막이에요.

우리는 흔히 변수가 언제든 변할 수 있다고 생각하지만, Rust는 기본적으로 모든 변수를 ‘한 번 정하면 바꿀 수 없는 상태’로 만들어 버려요. 이 개념을 제대로 이해하지 못하면 Rust라는 언어의 문턱을 넘기가 무척 힘들어요. 하지만 이 원리를 깨닫는 순간, 여러분의 코드는 이전과는 비교할 수 없을 정도로 안전하고 견고해질 거예요.

오늘 이 글에서는 Rust 변수와 가변성 사례를 통해 이 문제를 어떻게 우아하게 해결할 수 있는지 아주 자세히 다룰 예정이에요. 단순히 문법을 암기하는 것이 아니라, 실제 개발 상황에서 어떤 판단을 내려야 하는지에 집중해서 설명해 드릴게요.

💡 알아두기
Rust가 변수의 가변성을 엄격하게 관리하는 이유는 프로그램의 실행 중에 값이 예기치 않게 변해서 생기는 버그를 컴파일 단계에서 원천 차단하기 위해서예요.

이 글에서 함께 살펴볼 내용들

  • 변수의 기본 성질인 불변성이 왜 중요한지 이해하기
  • 가변성을 안전하게 추가하는 방법과 주의할 점
  • 섀도잉을 활용해 변수의 타입을 안전하게 바꾸는 기술
  • 실무 시나리오를 통해 배우는 변수 관리 전략

변수의 성질을 결정하기 전 꼭 알아야 할 기초 지식

본격적으로 코드를 작성하기 전에, 우리가 어떤 도구를 사용할 수 있는지 명확히 알아야 해요. Rust에서 변수를 다룬다는 것은 단순히 메모리에 공간을 만드는 것을 넘어, 그 공간에 대한 권한을 어떻게 설정할 것인가의 문제와 같아요.

우선 가장 중요한 개념은 불변성(Immutability)가변성(Mutability)이에요. 불변성은 값이 한 번 할당되면 절대 변하지 않는 성질을 말하고, 가변성은 프로그램 실행 도중에 값을 바꿀 수 있는 성질을 뜻해요. Rust는 기본적으로 불변성을 선택해요. 즉, 우리가 명시적으로 요청하지 않는 한 모든 변수는 철벽 수비 중인 성벽과 같다고 생각하면 돼요.

왜 굳이 이렇게 불편한 방식을 택했을까요? 데이터가 변하지 않는다는 보장이 있으면, 여러 함수나 스레드에서 동시에 그 데이터를 읽더라도 값이 중간에 바뀔 걱정을 할 필요가 없기 때문이에요. 이는 대규모 프로젝트에서 데이터의 흐름을 추적하기 훨씬 쉽게 만들어 주지요.

불변성과 가변성 선택 기준 비교

구분 기준 불변 변수 (let) 가변 변수 (let mut)
기본 설정 Rust의 기본값 (Default) 사용자가 명시적으로 선언
데이터 안전성 매우 높음 (예측 가능) 상대적으로 낮음 (주의 필요)
적합한 용도 설정값, 상수, 변하지 않는 데이터 카운터, 누적 합계, 상태 변화
컴파일러 체크 값 변경 시도 시 즉시 차단 허용하지만 가변성 범위 감시

변수를 선언할 때 우리는 스스로에게 질문을 던져야 해요. “이 값이 프로그램이 끝날 때까지 변할 일이 있는가?” 만약 대답이 ‘아니오’라면, 고민할 필요도 없이 일반적인 `let` 키워드를 사용해야 해요. 만약 ‘예’라면, 그때 비로소 `mut`라는 열쇠를 꺼내 들어야 하는 것이지요.

⚠️ 주의
무분별하게 모든 변수에 `mut`를 붙이는 습관은 지양해야 해요. 가변성이 많아질수록 코드의 어디에서 데이터가 변했는지 찾기가 힘들어지고, 이는 곧 버그의 온상이 됩니다.

또한, 변수의 생명 주기(Scope)에 대해서도 미리 인지하고 있어야 해요. 변수는 자신이 선언된 중괄호 `{ }` 안에서만 살아있다는 점을 잊지 마세요. 변수의 가변성은 해당 스코프 내에서만 유효하며, 스코프를 벗어나면 그 권한도 함께 사라진답니다.

실전 사례로 배우는 Rust 변수 관리 4단계

이론은 충분히 익혔으니 이제 실제 코드가 돌아가는 논리를 살펴볼 차례예요. 단순히 문법을 적는 게 아니라, 실제 서비스의 비즈니스 로직을 구현한다고 가정하고 단계별로 접근해 볼게요. 우리는 지금 간단한 사용자 포인트 관리 시스템을 만들고 있다고 상상해 보세요.

STEP 1. 기본 불변성으로 시스템 안정성 확보하기

포인트 시스템에서 가장 중요한 것은 사용자의 고유 ID나 가입 날짜처럼 절대 변해서는 안 되는 정보예요. 이런 정보들은 반드시 불변 변수로 선언해야 해요. 만약 실수로 사용자 ID가 프로그램 중간에 바뀐다면, 데이터베이스의 다른 정보와 연결이 끊어지는 대참사가 발생할 수 있거든요.

먼저, 사용자 정보를 담는 변수들을 선언해 볼게요. 이때 `let` 키워드만 사용하면 Rust는 이 값들을 읽기 전용으로 잠가버려요. 이렇게 하면 다른 개발자가 이 코드를 보더라도 “아, 이 값은 시스템 운영 중에 절대 바뀌지 않는구나”라고 안심하고 코드를 읽을 수 있어요. 이것이 바로 Rust가 추구하는 의도 기반 프로그래밍의 핵심이에요.

예를 들어, 사용자의 이름, 가입일, 초기 등급 같은 정보들은 모두 불변 상태로 유지돼요. 이 단계에서는 데이터의 무결성을 지키는 것에 모든 초점을 맞추는 것이지요. 만약 누군가 실수로 `user_id = 1002;`처럼 값을 바꾸려고 시도하면, 컴파일러는 즉시 실행을 중단하고 경고를 보내줄 거예요.

STEP 2. mut 키워드를 이용한 동적 데이터 처리

하지만 모든 것이 고정되어 있다면 시스템은 아무것도 할 수 없겠죠? 사용자가 쇼핑을 해서 포인트를 쌓거나, 쿠폰을 사용해서 포인트를 차감해야 하는 상황이 반드시 생겨요. 이때 필요한 것이 바로 가변성(mut)이에요.

포인트 잔액을 담는 변수는 `let mut balance = 1000;`과 같이 선언해야 해요. 이렇게 하면 `balance = balance + 500;`처럼 값을 업데이트하는 것이 가능해져요. 여기서 주의할 점은 가변성을 부여하는 범위예요. 포인트 잔액처럼 변화가 잦은 데이터에만 `mut`를 적용하고, 나머지 고정 데이터는 철저히 불변으로 남겨두는 균형 감각이 필요해요.

가변 변수를 사용할 때는 항상 데이터의 흐름을 머릿속으로 그려봐야 해요. 이 값이 어디서 변하는지, 어떤 조건에서 값이 바뀌는지 명확히 정의하지 않으면, 나중에 포인트가 왜 갑자기 0원이 되었는지 추적하는 데 엄청난 시간을 허비하게 될 거예요.

STEP 3. 섀도잉(Shadowing)을 활용한 스마트한 변수 재사용

가끔 우리는 변수의 값을 바꾸고 싶은 게 아니라, 변수의 데이터 타입 자체를 바꾸고 싶을 때가 있어요. 예를 들어, 사용자로부터 입력받은 문자열 형태의 포인트를 숫자로 변환해야 하는 경우예요. 이때 많은 입문자가 `mut`를 써서 타입을 바꾸려다 실패하곤 해요. 하지만 Rust에는 아주 멋진 해결책인 섀도잉이 있어요.

섀도잉은 동일한 이름의 변수를 다시 선언하는 기법이에요. `let` 키워드를 다시 사용하여 기존 변수의 이름을 그대로 쓰되, 새로운 타입의 값을 담는 것이지요. 이는 기존 변수를 완전히 가려버리고 새로운 변수를 만드는 방식이라서, 이전 변수의 값은 더 이상 접근할 수 없게 돼요.

이 방식의 가장 큰 장점은 변수의 이름은 유지하면서도, 데이터의 형식을 안전하게 전환할 수 있다는 점이에요. 예를 들어 문자열 타입인 `input_data`를 정수 타입인 `input_data`로 섀도잉하면, 코드의 가독성을 해치지 않으면서도 불필요한 새로운 이름(예: `input_data_int`)을 만들지 않아도 돼요. 이는 메모리 효율성 측면에서도 매우 유리한 방식이에요.

STEP 4. 실제 시나리오 적용: 포인트 결제 로직 구현하기

이제 지금까지 배운 내용을 하나의 시나리오로 통합해 볼게요. 사용자가 상품을 구매하는 과정을 코드로 설계해 봅시다.

💡 실전 시나리오 흐름
1. 사용자 정보(ID, 이름)는 불변(let)으로 선언하여 보호합니다.
2. 현재 포인트 잔액은 가변(let mut)으로 선언하여 차감 로직을 준비합니다.
3. 입력받은 상품 가격(문자열)은 섀도잉을 통해 정수형 변수로 변환합니다.
4. 결제 성공 시 잔액을 업데이트하고, 실패 시 에러를 반환합니다.

이 시나리오를 따라가면 우리는 코드의 각 부분에서 왜 이 변수가 가변적이어야 하는지, 혹은 왜 불변이어야 하는지 명확한 근거를 갖게 돼요. 사용자 ID는 변하지 않아야 하므로 불변, 포인트는 변해야 하므로 가변, 입력값은 변환이 필요하므로 섀도잉! 이 흐름을 이해했다면 여러분은 이미 Rust의 변수 관리 원칙을 실무 수준으로 적용할 준비가 된 거예요.

자주 하는 실수와 해결법

실전에서 개발자들이 가장 자주 마주치는 실수들을 정리했어요. 이 패턴들만 피해 가도 컴파일러와의 싸움에서 절반은 이긴 셈이에요.

  • 불변 변수에 값을 다시 할당하려고 할 때
    왜 발생하는가: Rust의 기본 설정은 불변이기 때문에, `mut` 없이 값을 바꾸려 하면 컴파일러가 거부해요.
    ✅ 해결법: 값을 변경해야 하는 의도가 확실하다면 변수 선언 시 `let mut`를 사용하세요.
  • 섀도잉 대신 가변성을 남용할 때
    왜 발생하는가: 타입 변환이 필요한 상황에서도 단순히 `mut`만 사용해 해결하려고 하면 타입 불일치 에러가 발생해요.
    ✅ 해결법: 데이터의 타입이 바뀌어야 한다면 `let` 키워드를 다시 사용하는 섀도잉 기법을 적용하세요.
  • 불필요하게 너무 많은 변수에 `mut`를 부여할 때
    왜 발생하는가: 모든 변수를 가변으로 만들면 데이터의 흐름을 추적하기 어려워지고 버그가 숨어들기 쉬워져요.
    ✅ 해결법: ‘최소 권한의 원칙’을 따르세요. 꼭 필요한 경우에만 `mut`를 붙이고 나머지는 불변으로 두세요.
  • 변수의 스코프를 오해할 때
    왜 발생하는가: 특정 블록 `{ }` 안에서 만든 변수를 블록 밖에서 사용하려 하면 컴파일러가 찾을 수 없다고 말해요.
    ✅ 해결법: 변수가 필요한 범위(Scope)를 미리 설계하고, 필요하다면 변수를 선언하는 위치를 조정하세요.
  • 가변 참조와 불변 참조를 혼용할 때
    왜 발생하는가: Rust의 소유권 규칙에 따라, 가변 참조자가 있는 동안에는 다른 읽기 전용 참조자를 만들 수 없어요.
    ✅ 해결법: 데이터에 대한 접근 권한을 명확히 나누고, 참조의 생명 주기를 짧게 유지하세요.

자주 묻는 질문

Q. 섀도잉을 사용하면 기존 변수의 메모리가 바로 해제되나요?
새로운 변수가 이전 변수의 이름을 가로채는 것이므로, 이전 변수는 더 이상 이름으로 접근할 수 없게 돼요. 메모리 해제는 Rust의 소유권 시스템에 따라 스코프가 종료될 때 자동으로 이루어집니다.

Q. `let mut`를 사용하면 데이터가 무조건 위험해지나요?
아니요, 그렇지 않아요. 가변성은 프로그램의 동작을 위해 반드시 필요한 기능이에요. 다만, 가변성을 사용할 때는 그 데이터가 언제, 어떻게 변하는지를 명확히 관리해야 한다는 책임이 따를 뿐이에요.

Q. 상수를 선언할 때도 `let`을 사용하나요?
상수는 `const` 키워드를 사용해요. `let`으로 만든 불변 변수와 `const`는 비슷해 보이지만, 상수는 컴파일 타임에 결정되어야 하며 타입 명시가 필수라는 점이 달라요.

Q. 초보자는 어떤 방식부터 연습하는 게 좋을까요?
처음에는 무조건 모든 변수를 불변(`let`)으로 선언하는 연습부터 하세요. 그러다 컴파일 에러가 발생하면, “이게 정말 변해야 하는 값인가?”를 스스로 질문한 뒤에만 `mut`를 추가하는 습관을 들이는 것이 가장 빠르게 성장하는 길이에요.

안전한 Rust 코딩을 위한 마지막 체크리스트

오늘 우리는 Rust의 핵심 중 하나인 변수와 가변성 개념을 깊이 있게 살펴보았어요. 이 내용들을 머릿속에 담아두면, 앞으로 Rust 코드를 짤 때 훨씬 더 자신감이 생길 거예요. 마지막으로 내용을 정리해 드릴게요.

✅ 핵심 요약

  • 기본은 항상 불변(`let`)으로 선언하여 데이터의 안정성을 확보하세요.
  • 값이 변해야 하는 경우에만 명시적으로 `let mut`를 사용하세요.
  • 타입을 변경해야 할 때는 섀도잉(Shadowing) 기법을 활용하세요.
  • 가변성은 최소한으로, 변수 스코프는 최대한 짧게 유지하는 것이 좋아요.
  • 에러 메시지는 여러분을 괴롭히는 게 아니라, 잠재적 버그를 막아주는 고마운 경고예요.

자, 이제 이론은 충분해요. 이제 직접 키보드를 잡고 코드를 작성해 볼 시간이에요. 처음에는 컴파일러의 경고가 귀찮게 느껴질 수도 있지만, 그 경고를 하나씩 해결해 나가다 보면 어느새 Rust의 철학에 동화된 자신을 발견하게 될 거예요.

지금 바로 실행해 보세요

  • 오늘 할 일: Rust 설치를 완료하고, `let`과 `let mut`를 사용한 아주 간단한 계산기 프로그램을 만들어 보세요.
  • 이번 주 할 일: 변수의 섀도잉을 활용해 문자열 입력을 숫자로 바꾸는 로직을 구현해 보세요.
  • 실행 직전 할 일: 내가 작성한 코드 중 `mut`가 붙은 변수가 정말 필요한 것인지 다시 한번 검토해 보세요.

실전 사례에서 Rust 변수와 가변성 적용 노하우를 얻어 가셨기를 바라요. 여러분의 안전하고 즐거운 Rust 학습을 응원합니다!

관련해서 더 깊이 있는 내용이 궁금하다면 Rust 변수와 가변성 관련 다른 글이나 입문 Rust 학습 가이드를 참고해 보세요.

댓글 남기기