이미지 로딩 중...
AI Generated
2025. 11. 13. · 4 Views
Rust 입문 가이드 13 변수와 가변성 완벽 이해
Rust의 기본이자 가장 중요한 개념인 변수와 가변성에 대해 알아봅니다. let과 mut 키워드를 통해 Rust가 어떻게 안전성과 유연성을 동시에 제공하는지 실무 예제와 함께 깊이 있게 다룹니다.
목차
- 불변 변수(Immutable Variable) - Rust의 기본 철학
- 가변 변수(Mutable Variable) - 필요할 때만 변경 허용
- 상수(Constants) - 변하지 않는 진정한 값
- 변수 섀도잉(Shadowing) - 같은 이름으로 새 변수 만들기
- 타입 명시와 추론 - Rust의 똑똑한 타입 시스템
- 변수 스코프와 생명주기 - 변수가 살아있는 범위
- 변수 바인딩과 패턴 매칭 - let의 숨겨진 힘
- 변수 네이밍 컨벤션 - Rust 스타일 가이드
1. 불변 변수(Immutable Variable) - Rust의 기본 철학
시작하며
여러분이 JavaScript나 Python으로 코드를 작성하다가 실수로 변수 값을 덮어쓴 경험이 있나요? 예를 들어, 사용자의 최초 입력값을 저장해두었는데, 나중에 실수로 그 변수에 다른 값을 할당해서 원본 데이터를 잃어버린 적 말입니다.
이런 문제는 실제 개발 현장에서 매우 흔하게 발생합니다. 특히 코드가 길어지고 여러 함수를 거치면서 변수가 예상치 못하게 변경되면, 버그를 찾기가 정말 어려워집니다.
데이터 무결성이 깨지고, 프로그램의 동작을 예측하기 힘들어지죠. 바로 이럴 때 필요한 것이 Rust의 불변 변수입니다.
Rust는 기본적으로 모든 변수를 불변으로 만들어서, 여러분이 의도하지 않은 데이터 변경을 컴파일 시점에서 차단합니다.
개요
간단히 말해서, Rust에서 let으로 선언한 변수는 기본적으로 불변(immutable)입니다. 한번 값을 할당하면 그 값을 변경할 수 없습니다.
왜 이런 설계를 택했을까요? Rust는 안전성(safety)과 동시성(concurrency)을 핵심 가치로 삼는 언어입니다.
불변 변수를 기본으로 하면, 여러 스레드에서 동시에 같은 데이터를 읽어도 안전하고, 코드의 동작을 예측하기 쉬워집니다. 예를 들어, 웹 서버에서 설정 값을 여러 요청 핸들러가 동시에 읽는 경우, 그 값이 변하지 않는다는 보장이 있으면 안심하고 코드를 작성할 수 있습니다.
기존 언어들에서는 변수를 선언하면 기본적으로 언제든 값을 바꿀 수 있었습니다. 하지만 Rust는 정반대입니다.
변하지 않는 것이 기본이고, 변해야 한다면 명시적으로 표시해야 합니다. 불변 변수의 핵심 특징은 세 가지입니다: 첫째, 컴파일 타임에 안전성을 검증합니다.
둘째, 코드를 읽는 사람이 "이 값은 변하지 않는구나"라고 즉시 이해할 수 있습니다. 셋째, 멀티스레드 환경에서 데이터 레이스(data race) 없이 안전하게 공유할 수 있습니다.
이러한 특징들이 대규모 시스템을 안정적으로 구축하는 데 매우 중요합니다.
코드 예제
// 사용자 정보를 저장하는 불변 변수
let username = "rust_learner";
let user_id = 12345;
// 이 값들은 프로그램 실행 중 변경되지 않습니다
println!("사용자: {}, ID: {}", username, user_id);
// 아래 코드는 컴파일 에러를 발생시킵니다!
// username = "new_name"; // error: cannot assign twice to immutable variable
// 설정 값도 불변으로 관리
let max_connections = 100;
let timeout_seconds = 30;
설명
이것이 하는 일: 불변 변수는 프로그램 실행 중에 값이 변경되지 않도록 보장하여, 데이터의 안전성과 코드의 예측 가능성을 높입니다. 첫 번째로, let 키워드로 변수를 선언하면 Rust 컴파일러는 그 변수를 불변으로 표시합니다.
username이나 user_id 같은 변수에 값을 할당한 후, 다시 값을 할당하려고 하면 컴파일러가 즉시 에러를 발생시킵니다. 이것은 런타임이 아닌 컴파일 타임에 발생하므로, 프로그램을 실행하기 전에 문제를 발견할 수 있습니다.
그 다음으로, 불변 변수를 사용하면 코드를 읽는 사람(미래의 여러분 포함)이 "이 값은 중간에 바뀌지 않는구나"라고 확신할 수 있습니다. 예를 들어, max_connections 변수를 보면 "이 프로그램에서 최대 연결 수는 100으로 고정되어 있구나"라고 즉시 이해할 수 있죠.
함수 안에서 수십 줄의 코드가 있어도, 이 값이 변하지 않는다는 것을 알기 때문에 코드를 추적하기가 훨씬 쉬워집니다. 마지막으로, 불변 변수는 멀티스레드 프로그래밍에서 엄청난 이점을 제공합니다.
여러 스레드가 동시에 같은 데이터를 읽을 때, 그 데이터가 불변이라면 락(lock)이나 뮤텍스(mutex) 없이도 안전하게 접근할 수 있습니다. 웹 서버를 만든다고 생각해보세요.
수백 개의 동시 요청이 들어올 때, 설정 값을 읽어야 한다면? 불변 변수로 선언해두면 동기화 오버헤드 없이 빠르고 안전하게 처리할 수 있습니다.
여러분이 불변 변수를 사용하면 버그가 줄어들고, 코드 리뷰가 쉬워지며, 리팩토링할 때 자신감이 생깁니다. 또한 컴파일러가 더 공격적으로 최적화할 수 있어서 성능도 향상될 수 있습니다.
Rust의 불변성은 단순한 제약이 아니라, 더 나은 소프트웨어를 만들기 위한 강력한 도구입니다.
실전 팁
💡 변수를 선언할 때 "이 값이 정말 변해야 하나?"라고 먼저 자문해보세요. 대부분의 경우 불변으로 충분하고, 코드가 더 안전해집니다.
💡 함수 파라미터도 기본적으로 불변입니다. 함수 안에서 파라미터 값을 변경하려고 하면 에러가 발생하니, 필요하다면 로컬 변수를 새로 만드세요.
💡 불변 변수를 사용하면 컴파일러가 더 많은 최적화를 수행할 수 있어 성능이 향상될 수 있습니다. 특히 인라인화(inlining)와 상수 폴딩(constant folding)에 유리합니다.
💡 디버깅할 때 불변 변수는 큰 도움이 됩니다. 값이 변하지 않으니 브레이크포인트에서 값을 확인했을 때, 그 값이 프로그램 시작부터 끝까지 동일함을 보장받을 수 있습니다.
2. 가변 변수(Mutable Variable) - 필요할 때만 변경 허용
시작하며
여러분이 게임을 만들고 있다고 상상해보세요. 플레이어의 점수, 체력, 위치 등은 게임이 진행되면서 계속 변해야 합니다.
그런데 모든 변수가 불변이라면? 매번 새로운 변수를 만들어야 하니 비효율적이고 코드도 복잡해질 겁니다.
실제로 프로그램을 작성하다 보면 상태(state)를 추적해야 하는 경우가 매우 많습니다. 카운터를 증가시키거나, 누적 합계를 계산하거나, 반복문에서 인덱스를 업데이트하는 등의 작업들이죠.
이런 경우 변수의 값을 변경할 수 있어야 합니다. 바로 이럴 때 필요한 것이 mut 키워드입니다.
Rust는 여러분이 명시적으로 "이 변수는 변할 수 있다"고 선언할 수 있게 해줍니다. 이렇게 하면 안전성은 유지하면서도 필요한 유연성을 확보할 수 있습니다.
개요
간단히 말해서, mut 키워드를 let과 함께 사용하면 가변(mutable) 변수를 만들 수 있습니다. 이 변수는 생명주기 동안 여러 번 값을 변경할 수 있습니다.
왜 이런 방식이 좋을까요? 첫째, 코드를 읽는 사람이 "아, 이 변수는 값이 변할 수 있구나"라고 즉시 알 수 있습니다.
둘째, 실수로 변경하면 안 되는 변수를 바꾸는 것을 방지합니다. 셋째, 컴파일러가 가변 참조(mutable reference)를 추적할 수 있어서 데이터 레이스를 방지할 수 있습니다.
예를 들어, 웹 애플리케이션에서 요청 카운터를 관리하거나, 실시간으로 통계를 계산하는 경우에 매우 유용합니다. 기존 언어들에서는 모든 변수가 기본적으로 가변이어서, 어떤 변수가 변할지 코드를 끝까지 읽어봐야 알 수 있었습니다.
하지만 Rust에서는 mut이 붙은 변수만 변할 수 있다는 것을 선언부에서 바로 확인할 수 있습니다. 가변 변수의 핵심 특징: 첫째, 명시적 선언이 필요합니다(let mut).
둘째, 타입은 변경할 수 없고 값만 변경할 수 있습니다. 셋째, Rust의 소유권 시스템과 결합되어 안전하게 관리됩니다.
이러한 특징들이 유연성과 안전성을 동시에 제공합니다.
코드 예제
// 게임 플레이어의 상태를 가변 변수로 관리
let mut score = 0;
let mut health = 100;
let mut level = 1;
// 게임이 진행되면서 값들이 변경됩니다
score += 50; // 점수 증가
health -= 10; // 데미지 받음
println!("점수: {}, 체력: {}", score, health);
// 레벨업 조건 확인
if score >= 100 {
level += 1;
println!("레벨업! 현재 레벨: {}", level);
}
설명
이것이 하는 일: 가변 변수는 프로그램 실행 중에 값을 여러 번 변경할 수 있게 해주며, mut 키워드를 통해 명시적으로 선언해야 합니다. 첫 번째로, let mut으로 변수를 선언하면 Rust 컴파일러는 "이 변수는 값이 변할 수 있다"고 표시합니다.
score, health, level 같은 게임 상태 변수들은 게임이 진행되면서 계속 업데이트되어야 하므로 가변으로 선언합니다. 이렇게 하면 += 연산자나 직접 할당을 통해 값을 변경할 수 있습니다.
그 다음으로, 가변 변수를 사용할 때 중요한 점은 타입은 변경할 수 없다는 것입니다. score를 정수(i32)로 선언했다면, 나중에 문자열을 할당할 수 없습니다.
이것은 타입 안전성을 보장하면서도 값의 변경은 허용하는 Rust의 균형 잡힌 접근 방식입니다. 만약 타입까지 바꾸고 싶다면 shadowing(뒤에서 배울 개념)을 사용해야 합니다.
세 번째로, 가변 변수는 Rust의 소유권 시스템과 긴밀하게 연결되어 있습니다. 예를 들어, 한 번에 하나의 가변 참조만 허용하는 규칙 덕분에, 여러 곳에서 동시에 같은 변수를 수정하려고 하면 컴파일 에러가 발생합니다.
이것이 데이터 레이스를 컴파일 타임에 방지하는 Rust의 핵심 메커니즘입니다. 마지막으로, 가변 변수를 사용하면 반복문, 카운터, 누적 계산 등 상태를 추적하는 모든 작업을 자연스럽게 표현할 수 있습니다.
하지만 Rust는 여러분에게 "정말 이 변수가 변해야 하나?"라고 생각하게 만듭니다. 이런 사고방식이 더 나은 코드 설계로 이어집니다.
여러분이 가변 변수를 적절히 사용하면, 필요한 곳에서는 유연성을 확보하고, 필요 없는 곳에서는 안전성을 유지하는 균형 잡힌 코드를 작성할 수 있습니다. 또한 mut 키워드가 코드 리뷰어에게 "여기는 상태가 변경되는 부분이니 주의 깊게 봐야 해"라는 신호를 보내는 역할도 합니다.
실전 팁
💡 가변 변수는 최소한으로 사용하세요. 변경 가능한 상태가 많을수록 코드를 이해하고 디버깅하기 어려워집니다. 먼저 불변으로 시도해보고, 정말 필요할 때만 mut을 추가하세요.
💡 반복문에서 누적 계산을 할 때 가변 변수가 매우 유용합니다. 예를 들어 배열의 합계를 계산할 때 let mut sum = 0; 으로 시작해서 값을 누적하는 패턴이 일반적입니다.
💡 가변 변수의 스코프를 최대한 좁게 유지하세요. 함수 전체에서 사용하는 것보다 블록 안에서만 사용하면 side effect를 줄일 수 있습니다.
💡 여러 스레드에서 가변 변수를 공유해야 한다면 Mutex나 Arc 같은 동기화 도구를 사용해야 합니다. 단순히 mut만으로는 스레드 안전성이 보장되지 않습니다.
💡 디버깅 시 가변 변수에 중단점을 설정하고 값의 변화를 추적하면, 로직 오류를 찾는 데 큰 도움이 됩니다.
3. 상수(Constants) - 변하지 않는 진정한 값
시작하며
여러분의 애플리케이션에서 파이(π) 값이나 최대 사용자 수 같은 설정이 필요하다고 해보죠. 이런 값들은 프로그램이 시작될 때부터 끝날 때까지 절대 변하지 않습니다.
컴파일 시점에 이미 정해진 값이죠. 불변 변수와 상수는 비슷해 보이지만 중요한 차이가 있습니다.
불변 변수는 런타임에 값이 결정될 수 있지만, 상수는 컴파일 타임에 반드시 값이 확정되어야 합니다. 또한 상수는 프로그램 전체에서 사용할 수 있는 전역 값으로 사용할 수 있습니다.
바로 이럴 때 필요한 것이 const 키워드입니다. Rust의 상수는 타입 안전성을 보장하면서도 컴파일러가 최적화할 수 있는 여지를 제공합니다.
개요
간단히 말해서, const로 선언한 상수는 컴파일 타임에 값이 결정되며, 프로그램 실행 중에 절대 변경할 수 없는 값입니다. 상수를 사용하는 이유는 명확합니다.
첫째, 매직 넘버(magic number)를 없애고 코드의 가독성을 높입니다. 코드에 3.14159가 흩어져 있는 것보다 PI라는 이름으로 사용하는 게 훨씬 낫습니다.
둘째, 값을 한 곳에서 관리할 수 있어 유지보수가 쉽습니다. 셋째, 컴파일러가 상수 값을 인라인으로 치환할 수 있어 성능상 이점이 있습니다.
예를 들어, 물리 시뮬레이션에서 중력 가속도 값이나, 네트워크 애플리케이션에서 타임아웃 값을 상수로 정의하면 유용합니다. 불변 변수(let)는 함수 안에서 선언되고 런타임에 값이 결정될 수 있지만, 상수(const)는 전역 스코프에서 선언할 수 있고 컴파일 타임 상수 표현식만 사용할 수 있습니다.
상수의 핵심 특징: 첫째, 타입을 명시적으로 지정해야 합니다. 둘째, 대문자 스네이크 케이스 명명 규칙을 따릅니다(예: MAX_POINTS).
셋째, 전역 스코프나 모듈 수준에서 선언할 수 있습니다. 넷째, 컴파일 타임 상수 표현식만 값으로 사용할 수 있습니다.
이러한 특징들이 코드의 명확성과 성능을 동시에 향상시킵니다.
코드 예제
// 수학 상수들
const PI: f64 = 3.141592653589793;
const E: f64 = 2.718281828459045;
// 애플리케이션 설정 상수
const MAX_USERS: u32 = 10_000;
const TIMEOUT_SECONDS: u64 = 30;
const APP_VERSION: &str = "1.0.0";
// 상수는 프로그램 어디서든 사용 가능
fn calculate_circle_area(radius: f64) -> f64 {
PI * radius * radius
}
println!("최대 사용자 수: {}", MAX_USERS);
println!("원의 넓이: {}", calculate_circle_area(5.0));
설명
이것이 하는 일: 상수는 프로그램 전체에서 변하지 않는 값을 정의하며, 컴파일 타임에 값이 확정되어 런타임 오버헤드 없이 사용할 수 있습니다. 첫 번째로, const 키워드로 상수를 선언할 때는 반드시 타입을 명시해야 합니다.
PI: f64처럼 말이죠. 이것은 타입 추론이 불가능하기 때문이 아니라, 상수의 사용이 명확하고 예측 가능하도록 하기 위함입니다.
또한 상수 이름은 관례적으로 대문자 스네이크 케이스를 사용합니다. MAX_USERS, TIMEOUT_SECONDS처럼요.
이렇게 하면 코드를 읽을 때 "이것은 상수구나"라고 즉시 알 수 있습니다. 그 다음으로, 상수의 값은 컴파일 타임에 계산 가능한 표현식이어야 합니다.
즉, 함수 호출 결과나 런타임에만 알 수 있는 값을 사용할 수 없습니다. 3.141592나 10_000 같은 리터럴 값, 또는 다른 상수들을 조합한 산술 연산은 가능합니다.
예를 들어, const MAX_SIZE: usize = 1024 * 1024; 같은 표현은 가능하지만, const CURRENT_TIME: u64 = get_current_time(); 같은 것은 불가능합니다. 세 번째로, 상수는 전역 스코프에 선언할 수 있어서 모듈 전체나 심지어 pub을 붙이면 다른 모듈에서도 사용할 수 있습니다.
calculate_circle_area 함수에서 PI 상수를 사용하는 것처럼, 어디서든 접근 가능합니다. 이것은 설정 값이나 공통 상수를 여러 곳에서 공유할 때 매우 유용합니다.
마지막으로, 컴파일러는 상수 값을 사용하는 모든 곳에 그 값을 직접 인라인으로 치환합니다. 즉, PI를 사용하는 곳마다 실제로 3.141592...
값이 직접 들어갑니다. 이것은 런타임에 변수를 읽는 오버헤드가 전혀 없다는 뜻입니다.
성능이 중요한 루프 안에서도 상수를 자유롭게 사용할 수 있습니다. 여러분이 상수를 적절히 사용하면 코드의 가독성이 크게 향상되고, 매직 넘버가 사라지며, 설정 값을 중앙에서 관리할 수 있습니다.
또한 컴파일러의 최적화 덕분에 성능 저하 없이 명확한 코드를 작성할 수 있습니다.
실전 팁
💡 설정 값이나 물리 상수처럼 프로그램 전체에서 사용되는 값은 상수로 정의하세요. 코드를 수정할 때 한 곳만 바꾸면 됩니다.
💡 숫자 리터럴에 언더스코어(_)를 사용하면 가독성이 좋아집니다. 1_000_000은 1000000보다 읽기 쉽습니다.
💡 상수는 스택이나 힙에 메모리를 할당하지 않습니다. 컴파일된 바이너리에 직접 포함되므로 메모리 효율적입니다.
💡 여러 파일에서 공통으로 사용하는 상수는 별도의 모듈(예: constants.rs)에 모아두면 관리하기 편합니다.
💡 const fn을 사용하면 컴파일 타임에 실행 가능한 함수를 정의할 수 있어, 복잡한 상수 값을 계산할 때 유용합니다.
4. 변수 섀도잉(Shadowing) - 같은 이름으로 새 변수 만들기
시작하며
여러분이 사용자 입력을 받아서 처리하는 코드를 작성한다고 해보죠. 처음에는 문자열로 입력을 받고, 그 다음 정수로 파싱하고, 마지막에는 그 값을 검증한 결과를 저장합니다.
input_str, input_num, validated_input처럼 매번 다른 이름을 붙이면 변수가 너무 많아지고 헷갈립니다. 실제 개발에서는 같은 데이터를 여러 단계로 변환하는 경우가 정말 많습니다.
문자열을 파싱하거나, 단위를 변환하거나, 포맷을 바꾸는 등의 작업이죠. 매번 새 이름을 짓는 것도 귀찮고, 코드도 복잡해집니다.
바로 이럴 때 유용한 것이 섀도잉(shadowing)입니다. Rust는 같은 이름으로 새로운 변수를 선언할 수 있게 해주며, 이를 통해 데이터 변환 파이프라인을 깔끔하게 표현할 수 있습니다.
개요
간단히 말해서, 섀도잉은 let 키워드를 사용해서 같은 이름의 변수를 다시 선언하는 것입니다. 이전 변수는 가려지고(shadowed), 새로운 변수가 그 이름을 차지합니다.
섀도잉이 유용한 이유는 여러 가지입니다. 첫째, 데이터 변환 과정을 단계별로 명확하게 표현할 수 있습니다.
둘째, 변수 이름을 재사용할 수 있어서 네이밍에 대한 고민이 줄어듭니다. 셋째, mut 없이도 값을 "변경"하는 효과를 낼 수 있습니다.
넷째, 타입까지 바꿀 수 있어서 매우 유연합니다. 예를 들어, API에서 받은 JSON 문자열을 파싱하고, 검증하고, 비즈니스 객체로 변환하는 과정을 모두 같은 변수 이름으로 표현할 수 있습니다.
가변 변수(mut)와는 다릅니다. mut은 같은 변수의 값을 변경하지만, 타입은 바꿀 수 없습니다.
반면 섀도잉은 실제로 새로운 변수를 만드는 것이므로 타입도 바꿀 수 있습니다. 섀도잉의 핵심 특징: 첫째, let 키워드를 사용해야 합니다.
둘째, 이전 변수와 다른 타입을 가질 수 있습니다. 셋째, 스코프 내에서만 유효하며, 스코프가 끝나면 이전 변수가 다시 나타납니다.
넷째, 불변성을 유지하면서도 값을 변환할 수 있습니다. 이러한 특징들이 함수형 프로그래밍 스타일과 잘 어울립니다.
코드 예제
// 사용자 입력을 단계별로 변환 - 섀도잉 사용
let user_input = "42"; // 문자열
println!("입력값(문자열): {}", user_input);
// 같은 이름으로 파싱 - 타입이 String에서 i32로 변경!
let user_input: i32 = user_input.parse().expect("숫자가 아닙니다");
println!("입력값(숫자): {}", user_input);
// 다시 같은 이름으로 계산 결과 저장
let user_input = user_input * 2;
println!("최종 결과: {}", user_input);
// 공백 문자열 처리 예제
let spaces = " "; // &str 타입
let spaces = spaces.len(); // usize 타입으로 변경
설명
이것이 하는 일: 섀도잉은 같은 이름으로 새로운 변수를 선언하여 이전 변수를 가리고, 타입 변환이나 값 변환을 자연스럽게 표현할 수 있게 해줍니다. 첫 번째로, 섀도잉을 사용하면 데이터 변환 파이프라인을 매우 직관적으로 표현할 수 있습니다.
user_input 예제를 보면, 처음에는 "&str" 타입의 문자열이었다가, parse()를 통해 i32 타입의 정수로 바뀌고, 마지막에는 계산된 결과를 저장합니다. 각 단계마다 user_input이라는 같은 이름을 사용하지만, 실제로는 세 개의 서로 다른 변수입니다.
이렇게 하면 "이 변수는 사용자 입력값이다"라는 의미를 계속 유지하면서도 타입과 값을 자유롭게 변경할 수 있습니다. 그 다음으로, 섀도잉과 mut의 중요한 차이를 이해해야 합니다.
mut을 사용하면 변수의 값만 바꿀 수 있고 타입은 고정됩니다. 예를 들어, let mut x = "5"; x = 10; 이런 코드는 에러가 발생합니다.
문자열을 정수로 바꿀 수 없으니까요. 하지만 섀도잉을 사용하면 let x = "5"; let x = 10; 이렇게 완전히 다른 타입으로 바꿀 수 있습니다.
이것이 섀도잉의 강력한 점입니다. 세 번째로, 섀도잉은 스코프와 깊은 관련이 있습니다.
블록({ }) 안에서 섀도잉을 하면, 그 블록이 끝날 때 이전 변수가 다시 나타납니다. 예를 들어, 함수 전체에서 사용하는 변수가 있는데, 중간에 짧은 계산을 위해 잠깐 다른 값을 사용하고 싶다면 블록으로 감싸서 섀도잉할 수 있습니다.
블록이 끝나면 원래 변수가 복원됩니다. 마지막으로, spaces 예제는 섀도잉의 실용적인 사용 예를 보여줍니다.
문자열의 공백 개수를 세는 상황에서, 처음에는 문자열 타입이 필요하고 나중에는 길이(숫자)가 필요합니다. 섀도잉을 사용하면 spaces_str과 spaces_len처럼 두 개의 변수를 만들 필요 없이, 같은 이름을 재사용할 수 있습니다.
여러분이 섀도잉을 활용하면 코드가 더 간결해지고, 변수 이름을 짓는 데 드는 시간이 줄어들며, 데이터 흐름이 명확해집니다. 특히 파싱, 변환, 검증 같은 여러 단계를 거치는 코드에서 매우 유용합니다.
실전 팁
💡 문자열을 숫자로 파싱할 때 섀도잉이 매우 유용합니다. 같은 의미의 데이터를 다른 타입으로 표현할 때 자연스럽습니다.
💡 섀도잉과 mut을 혼동하지 마세요. 타입을 바꿔야 한다면 섀도잉, 같은 타입에서 값만 여러 번 바꿔야 한다면 mut을 사용하세요.
💡 임시 변수가 필요한 경우 블록과 섀도잉을 조합하면 깔끔합니다. { let x = temp_calc(); process(x); } 처럼요.
💡 과도한 섀도잉은 오히려 코드를 헷갈리게 만들 수 있습니다. 3-4번 이상 섀도잉하면 새로운 이름을 고려해보세요.
💡 디버깅할 때 섀도잉된 변수는 각각 다른 변수이므로, 스코프를 잘 확인해야 합니다. 디버거가 어떤 "버전"의 변수를 보여주는지 주의하세요.
5. 타입 명시와 추론 - Rust의 똑똑한 타입 시스템
시작하며
여러분이 let x = 5;라고 코드를 작성했을 때, Rust는 어떻게 x가 정수라는 것을 알까요? TypeScript나 Java처럼 let x: number = 5;나 int x = 5;처럼 타입을 명시하지 않았는데 말이죠.
대부분의 정적 타입 언어들은 타입을 명시적으로 적어야 합니다. 하지만 Rust는 강력한 타입 추론 시스템을 가지고 있어서, 많은 경우 컴파일러가 타입을 자동으로 알아냅니다.
이것은 코드를 간결하게 유지하면서도 타입 안전성을 보장하는 Rust의 멋진 특징입니다. 하지만 모든 상황에서 타입 추론이 가능한 것은 아닙니다.
때로는 명시적으로 타입을 지정해야 하고, 또 때로는 명시하는 것이 코드의 가독성을 높입니다.
개요
간단히 말해서, Rust는 문맥을 보고 변수의 타입을 추론할 수 있지만, 필요하거나 원하는 경우 명시적으로 타입을 지정할 수도 있습니다. 타입 추론과 명시의 균형이 중요한 이유는 무엇일까요?
첫째, 간단한 경우에는 타입을 생략하면 코드가 깔끔해집니다. 둘째, 복잡하거나 애매한 경우에는 타입을 명시하면 의도가 명확해지고 에러를 방지할 수 있습니다.
셋째, 타입을 명시하면 나중에 코드를 읽는 사람(미래의 여러분 포함)이 변수의 용도를 빠르게 파악할 수 있습니다. 예를 들어, 금융 애플리케이션에서 금액을 다룰 때 f32와 f64 중 어떤 것을 사용하는지 명확히 하거나, 큰 정수를 다룰 때 i32가 아닌 i64를 사용한다는 것을 명시하면 유용합니다.
동적 타입 언어(Python, JavaScript)는 런타임에 타입이 결정되지만, Rust는 컴파일 타임에 모든 타입이 확정됩니다. 하지만 타입 추론 덕분에 코드 작성은 동적 언어처럼 간편합니다.
타입 시스템의 핵심 특징: 첫째, 컴파일러가 사용 패턴을 보고 타입을 추론합니다. 둘째, 타입 애노테이션(: i32)으로 명시적 지정이 가능합니다.
셋째, 타입이 애매한 경우 컴파일러가 에러를 발생시켜 명시를 요구합니다. 넷째, 한번 결정된 타입은 변경할 수 없습니다(섀도잉 제외).
이러한 특징들이 Rust 코드의 안전성과 표현력을 동시에 높입니다.
코드 예제
// 타입 추론 - 컴파일러가 자동으로 타입 결정
let age = 25; // i32로 추론됨
let price = 19.99; // f64로 추론됨
let is_active = true; // bool로 추론됨
// 타입 명시 - 명확성을 위해 직접 지정
let user_id: u64 = 123456789; // 큰 ID 값 처리
let temperature: f32 = 36.5; // 메모리 절약을 위해 f32 사용
let count: usize = 100; // 인덱스나 크기에 적합한 타입
// 타입 명시가 필요한 경우 - parse()의 결과 타입이 애매함
let number: i32 = "42".parse().expect("Not a number");
// 타입 접미사 사용
let explicit = 100u32; // u32 타입 명시
설명
이것이 하는 일: Rust의 타입 시스템은 변수 사용 패턴을 분석해서 타입을 추론하지만, 개발자가 명시적으로 타입을 지정할 수도 있어 유연성과 안전성을 모두 제공합니다. 첫 번째로, Rust 컴파일러는 매우 똑똑합니다.
let age = 25;라고 쓰면, 25가 정수 리터럴이고 다른 단서가 없으니 기본 정수 타입인 i32로 결정합니다. let price = 19.99;는 소수점이 있으니 기본 부동소수점 타입인 f64로 결정하죠.
이렇게 많은 경우 타입 애노테이션 없이도 컴파일러가 올바른 타입을 선택합니다. 그 다음으로, 타입을 명시하는 것이 더 나은 경우도 많습니다.
user_id: u64 예제를 보면, 단순히 숫자를 저장하는 것이 아니라 "매우 큰 범위의 사용자 ID"를 다룬다는 의도가 드러납니다. temperature: f32는 "높은 정밀도가 필요 없고 메모리를 절약하고 싶다"는 의도를 표현합니다.
이런 명시적 타입 지정은 코드를 자기 문서화(self-documenting)하게 만듭니다. 세 번째로, 타입 명시가 필수인 경우도 있습니다.
"42".parse()는 문자열을 파싱하는 함수인데, 어떤 타입으로 파싱할지 알 수 없습니다. i32일 수도, f64일 수도, u32일 수도 있으니까요.
이런 경우 반드시 타입을 명시해야 하며, 그렇지 않으면 컴파일 에러가 발생합니다. 이것은 Rust가 타입 안전성을 보장하는 방식입니다.
네 번째로, 타입 접미사(type suffix)라는 편리한 기능도 있습니다. 100u32처럼 리터럴 뒤에 직접 타입을 붙일 수 있습니다.
이것은 함수 호출이나 매크로 안에서 타입을 명확히 할 때 특히 유용합니다. vec![1u8, 2, 3]처럼 첫 번째 요소에만 타입을 붙이면 나머지도 같은 타입으로 추론됩니다.
여러분이 타입 추론과 명시를 적절히 활용하면, 간결한 코드를 작성하면서도 중요한 부분에서는 명확성을 유지할 수 있습니다. 경험이 쌓이면 언제 추론에 맡기고 언제 명시할지 자연스럽게 판단하게 됩니다.
실전 팁
💡 간단한 지역 변수는 타입 추론에 맡기고, 함수 시그니처(파라미터, 리턴 타입)는 항상 명시하세요. 이것이 Rust 커뮤니티의 일반적인 관행입니다.
💡 숫자 연산에서 타입 불일치 에러가 나면, 타입을 명시적으로 지정하거나 as 키워드로 캐스팅하세요.
💡 제네릭 함수를 사용할 때 타입을 명시해야 하는 경우가 많습니다. collect() 같은 함수가 대표적입니다.
💡 복잡한 타입(예: HashMap<String, Vec<i32>>)은 타입 별칭(type alias)을 만들면 코드가 깔끔해집니다.
💡 IDE나 rust-analyzer를 사용하면 추론된 타입을 바로 확인할 수 있어 학습에 매우 도움이 됩니다.
6. 변수 스코프와 생명주기 - 변수가 살아있는 범위
시작하며
여러분이 함수 안에서 변수를 선언했는데, 함수 밖에서 그 변수를 사용하려고 하면 어떻게 될까요? 당연히 에러가 발생하죠.
이것은 변수의 스코프(scope) 개념 때문입니다. 스코프는 프로그래밍의 기본 개념이지만, Rust에서는 특히 중요합니다.
왜냐하면 Rust의 소유권(ownership) 시스템이 스코프와 깊이 연결되어 있기 때문이죠. 변수가 스코프를 벗어나면 메모리가 자동으로 해제되고, 이것이 Rust의 메모리 안전성을 보장하는 핵심 메커니즘입니다.
스코프를 제대로 이해하면 변수의 생명주기를 제어할 수 있고, 메모리를 효율적으로 사용할 수 있으며, 예상치 못한 버그를 방지할 수 있습니다.
개요
간단히 말해서, 스코프는 변수가 유효한 코드의 범위입니다. 중괄호({ })로 만들어지며, 변수는 선언된 스코프 안에서만 사용할 수 있습니다.
스코프가 중요한 이유는 여러 가지입니다. 첫째, 변수의 생명주기를 명확하게 관리할 수 있습니다.
필요한 곳에서만 변수를 살려두고, 필요 없어지면 자동으로 정리됩니다. 둘째, 이름 충돌을 방지할 수 있습니다.
서로 다른 스코프에서는 같은 이름의 변수를 사용할 수 있습니다. 셋째, 메모리 관리가 자동으로 이루어집니다.
스코프가 끝나면 그 안의 변수들이 자동으로 drop되어 메모리가 해제됩니다. 예를 들어, 대용량 파일을 처리할 때 필요한 데이터 구조를 블록 안에서만 유지하면, 블록이 끝날 때 자동으로 메모리가 반환되어 효율적입니다.
가비지 컬렉션 언어(Java, Go)는 언제 메모리가 해제될지 예측하기 어렵지만, Rust는 스코프가 끝나는 시점에 정확히 해제됩니다. 수동 메모리 관리 언어(C, C++)는 개발자가 직접 해제해야 하지만, Rust는 자동입니다.
스코프의 핵심 특징: 첫째, 중괄호로 새로운 스코프를 만듭니다. 둘째, 변수는 선언 시점부터 스코프 끝까지 유효합니다.
셋째, 내부 스코프는 외부 스코프의 변수에 접근할 수 있습니다. 넷째, 스코프가 끝나면 그 안의 변수들이 자동으로 drop됩니다.
이러한 특징들이 Rust의 "제로 코스트 추상화"를 가능하게 합니다.
코드 예제
fn main() {
// 외부 스코프
let outer = "외부 변수";
println!("외부: {}", outer);
{
// 내부 스코프 시작
let inner = "내부 변수";
println!("내부에서 외부 접근: {}", outer); // OK
println!("내부: {}", inner);
// 섀도잉도 스코프 안에서만 유효
let outer = "가려진 외부";
println!("가려진 값: {}", outer);
} // 내부 스코프 끝 - inner가 drop됨
// println!("내부 변수: {}", inner); // 에러! inner는 이미 사라짐
println!("다시 외부: {}", outer); // 원래 outer가 다시 나타남
}
설명
이것이 하는 일: 스코프는 변수의 생명주기를 정의하며, 스코프가 끝나는 시점에 자동으로 메모리를 정리하여 안전하고 효율적인 메모리 관리를 제공합니다. 첫 번째로, 스코프는 중괄호({ })로 만들어집니다.
함수, if문, 루프, 심지어 독립적인 블록도 스코프를 만듭니다. outer 변수는 main 함수의 스코프에서 선언되었으므로 함수가 끝날 때까지 유효합니다.
반면 inner 변수는 중첩된 블록 안에서 선언되었으므로, 그 블록이 끝나는 닫는 중괄호에서 즉시 사라집니다. 그 다음으로, 내부 스코프는 외부 스코프의 변수에 접근할 수 있습니다.
이것을 렉시컬 스코핑(lexical scoping)이라고 합니다. 내부 블록에서 outer 변수를 읽을 수 있는 것이 그 예입니다.
하지만 반대는 안 됩니다. 외부 스코프에서 inner를 사용하려고 하면 컴파일 에러가 발생합니다.
왜냐하면 inner는 이미 메모리에서 해제되었기 때문입니다. 세 번째로, 섀도잉과 스코프의 관계를 보세요.
내부 블록에서 outer를 섀도잉하면, 그 블록 안에서는 새로운 outer가 사용됩니다. 하지만 블록이 끝나면 섀도잉된 변수가 사라지고 원래 outer가 다시 보입니다.
이것은 섀도잉이 새로운 변수를 만드는 것이며, 그 변수도 스코프의 규칙을 따른다는 증거입니다. 네 번째로, 스코프가 끝날 때 자동으로 drop이 호출된다는 것이 중요합니다.
단순 타입(정수, 부울 등)은 drop이 별로 중요하지 않지만, String, Vec, File 같은 타입은 힙 메모리나 시스템 리소스를 사용하므로 drop이 매우 중요합니다. 예를 들어, 파일을 열고 블록 안에서만 사용하면, 블록이 끝날 때 파일이 자동으로 닫힙니다.
이것이 Rust의 "RAII(Resource Acquisition Is Initialization)" 패턴입니다. 여러분이 스코프를 잘 활용하면 메모리 누수 없이 효율적인 코드를 작성할 수 있습니다.
특히 큰 데이터 구조를 다룰 때, 필요한 범위에서만 변수를 유지하면 메모리 사용량을 크게 줄일 수 있습니다.
실전 팁
💡 큰 데이터를 처리할 때는 의도적으로 블록을 만들어서 스코프를 좁히세요. 처리가 끝나면 즉시 메모리가 해제됩니다.
💡 파일이나 네트워크 연결 같은 리소스는 블록과 스코프를 활용해서 자동으로 정리되게 하세요. 명시적으로 닫지 않아도 됩니다.
💡 변수를 함수 시작 부분에 모두 선언하지 말고, 실제로 필요한 곳에서 선언하세요. 스코프가 짧을수록 코드를 이해하기 쉽습니다.
💡 디버깅할 때 변수가 "not found" 에러를 일으키면, 스코프를 먼저 확인하세요. 대부분 스코프 밖에서 사용하려고 한 경우입니다.
💡 루프 안에서 임시 변수를 선언하면, 매 반복마다 생성되고 해제됩니다. 성능이 중요하다면 루프 밖에서 선언하고 재사용하는 것도 고려하세요.
7. 변수 바인딩과 패턴 매칭 - let의 숨겨진 힘
시작하며
여러분은 지금까지 let x = 5;처럼 간단한 할당만 봤을 겁니다. 하지만 let은 훨씬 더 강력합니다.
튜플을 풀어서 여러 변수에 동시에 할당하거나, 구조체의 필드를 추출하거나, 배열의 일부만 가져올 수 있습니다. 실제 프로그래밍에서는 함수가 여러 값을 반환하거나, 복잡한 데이터 구조에서 필요한 부분만 추출하는 경우가 매우 흔합니다.
Python의 언패킹이나 JavaScript의 구조 분해 할당을 써본 적이 있다면, Rust의 패턴 매칭도 비슷하지만 더 강력하다고 느낄 겁니다. let은 단순한 할당이 아니라 패턴 매칭을 수행합니다.
이것을 이해하면 Rust 코드를 훨씬 더 간결하고 표현력 있게 작성할 수 있습니다.
개요
간단히 말해서, let은 패턴(pattern)을 사용해서 값을 분해하고 여러 변수에 동시에 바인딩할 수 있습니다. 패턴 매칭이 유용한 이유는 무엇일까요?
첫째, 코드가 매우 간결해집니다. 여러 줄의 할당문을 한 줄로 줄일 수 있습니다.
둘째, 의도가 명확해집니다. "이 튜플에서 첫 번째와 두 번째 값만 사용한다"는 것이 코드에서 바로 드러납니다.
셋째, 컴파일러가 패턴이 올바른지 검증해줍니다. 예를 들어, 좌표를 다루는 그래픽 프로그램에서 (x, y) 튜플을 받아서 바로 두 변수로 분리하거나, HTTP 응답에서 (status_code, body) 형태의 데이터를 깔끔하게 처리할 수 있습니다.
다른 언어들도 비슷한 기능을 제공하지만, Rust의 패턴 매칭은 타입 안전하며 컴파일 타임에 검증됩니다. Python의 언패킹은 런타임 에러를 일으킬 수 있지만, Rust는 컴파일 시점에 문제를 찾아냅니다.
패턴 매칭의 핵심 특징: 첫째, 튜플, 배열, 구조체 등 다양한 타입을 분해할 수 있습니다. 둘째, 언더스코어(_)로 필요 없는 값을 무시할 수 있습니다.
셋째, 불변성과 가변성을 개별 변수마다 지정할 수 있습니다. 넷째, 중첩된 구조도 한 번에 분해할 수 있습니다.
이러한 특징들이 복잡한 데이터 구조를 다룰 때 큰 힘을 발휘합니다.
코드 예제
// 튜플 분해
let (x, y, z) = (1, 2, 3);
println!("x: {}, y: {}, z: {}", x, y, z);
// 일부 값 무시하기
let (first, _, third) = (10, 20, 30);
println!("first: {}, third: {}", first, third);
// 함수 반환값 분해 - 매우 실용적!
fn get_user_info() -> (String, u32, bool) {
("Alice".to_string(), 25, true)
}
let (name, age, is_active) = get_user_info();
println!("User: {}, Age: {}, Active: {}", name, age, is_active);
// 배열 분해
let [a, b, c] = [100, 200, 300];
설명
이것이 하는 일: let의 패턴 매칭은 복잡한 데이터 구조를 간단한 문법으로 분해하여, 필요한 부분만 추출해서 개별 변수에 할당할 수 있게 해줍니다. 첫 번째로, 튜플 분해는 가장 흔하게 사용되는 패턴입니다.
let (x, y, z) = (1, 2, 3);은 오른쪽의 튜플을 왼쪽의 패턴과 매칭시킵니다. 첫 번째 요소 1은 x에, 두 번째 요소 2는 y에, 세 번째 요소 3은 z에 바인딩됩니다.
이것은 세 개의 let 문을 한 줄로 줄인 것과 같습니다. 컴파일러는 양쪽의 구조가 일치하는지 확인하므로, 요소 개수가 맞지 않으면 에러를 발생시킵니다.
그 다음으로, 언더스코어(_)를 사용하면 특정 값을 무시할 수 있습니다. let (first, _, third) = (10, 20, 30);에서 두 번째 값 20은 사용하지 않겠다는 의미입니다.
이것은 "나는 첫 번째와 세 번째 값만 필요하다"는 의도를 명확히 표현합니다. 만약 모든 값을 변수에 바인딩했는데 일부를 사용하지 않으면, Rust 컴파일러가 경고를 발생시킵니다.
언더스코어를 사용하면 이런 경고를 피할 수 있습니다. 세 번째로, 함수 반환값을 분해하는 패턴은 실무에서 매우 자주 사용됩니다.
get_user_info() 함수가 세 개의 값을 튜플로 반환하면, 호출하는 쪽에서 즉시 분해해서 의미 있는 변수 이름을 붙일 수 있습니다. 이것은 코드의 가독성을 크게 향상시킵니다.
result.0, result.1처럼 인덱스로 접근하는 것보다 name, age, is_active처럼 의미 있는 이름을 사용하는 것이 훨씬 낫습니다. 네 번째로, 배열도 비슷하게 분해할 수 있습니다.
let [a, b, c] = [100, 200, 300];처럼 대괄호를 사용합니다. 더 나아가, 복잡한 중첩 구조도 분해할 수 있습니다.
예를 들어, let ((x1, y1), (x2, y2)) = ((0, 0), (10, 20));처럼 두 개의 좌표 쌍을 한 번에 분해할 수 있습니다. 여러분이 패턴 매칭을 활용하면 코드가 간결해지고, 임시 변수가 줄어들며, 의도가 명확해집니다.
특히 함수가 여러 값을 반환하거나, 데이터를 변환하는 과정에서 매우 유용합니다.
실전 팁
💡 함수가 여러 값을 반환해야 한다면 튜플을 사용하고, 호출하는 쪽에서 패턴 매칭으로 분해하세요. 구조체보다 간편합니다.
💡 사용하지 않는 값은 반드시 언더스코어(_)로 표시하세요. 컴파일러 경고를 줄이고 의도를 명확히 합니다.
💡 복잡한 중첩 구조를 다룰 때는 너무 깊게 분해하지 마세요. 가독성이 오히려 떨어질 수 있습니다. 2단계 정도가 적당합니다.
💡 반복문에서도 패턴 매칭을 사용할 수 있습니다. for (key, value) in map.iter() { } 처럼요.
💡 패턴 매칭은 match 표현식에서 더욱 강력해집니다. let은 그 중 가장 간단한 형태일 뿐입니다.
8. 변수 네이밍 컨벤션 - Rust 스타일 가이드
시작하며
여러분이 코드를 작성할 때 변수 이름을 myVariable처럼 카멜 케이스로 쓰나요, 아니면 my_variable처럼 스네이크 케이스로 쓰나요? 각 프로그래밍 언어에는 커뮤니티에서 합의한 네이밍 컨벤션이 있습니다.
일관된 네이밍 컨벤션을 따르는 것은 단순히 스타일의 문제가 아닙니다. 코드를 읽는 사람이 변수의 용도를 즉시 파악할 수 있게 하고, 팀 협업을 원활하게 하며, 버그를 줄이는 데 도움이 됩니다.
Rust는 이런 부분에서 매우 명확한 가이드라인을 제공합니다. Rust 컴파일러는 네이밍 컨벤션을 따르지 않으면 경고를 발생시킵니다.
이것은 여러분이 자연스럽게 Rust 커뮤니티의 관행을 따르도록 유도합니다.
개요
간단히 말해서, Rust는 스네이크 케이스(snake_case)를 변수와 함수 이름에 사용하고, 대문자 스네이크 케이스(SCREAMING_SNAKE_CASE)를 상수에 사용하며, 파스칼 케이스(PascalCase)를 타입에 사용합니다. 네이밍 컨벤션이 중요한 이유는 무엇일까요?
첫째, 코드 리뷰가 쉬워집니다. 모든 Rust 개발자가 같은 스타일을 사용하므로, 낯선 코드베이스도 빠르게 이해할 수 있습니다.
둘째, 변수의 종류를 이름만 봐도 알 수 있습니다. MAX_SIZE를 보면 "상수구나", calculate_sum을 보면 "함수구나"라고 즉시 알 수 있죠.
셋째, 오픈소스 프로젝트에 기여할 때 일관성 있는 코드를 작성할 수 있습니다. 예를 들어, 웹 프레임워크나 라이브러리를 개발할 때 일관된 네이밍은 API 사용자 경험을 크게 향상시킵니다.
JavaScript는 카멜 케이스를, Python과 Rust는 스네이크 케이스를, Java와 C#는 파스칼 케이스를 선호합니다. 각 언어의 문화를 존중하는 것이 중요합니다.
네이밍 컨벤션의 핵심: 첫째, 변수와 함수는 소문자 스네이크 케이스(user_name, calculate_total). 둘째, 상수는 대문자 스네이크 케이스(MAX_CONNECTIONS, PI).
셋째, 타입(구조체, 열거형, 트레이트)은 파스칼 케이스(User, HttpResponse, ToString). 넷째, 의미 있고 서술적인 이름을 사용합니다.
이러한 규칙들이 Rust 생태계 전체의 일관성을 만듭니다.
코드 예제
// 변수와 함수 - 소문자 스네이크 케이스
let user_name = "Alice";
let total_count = 100;
let is_valid = true;
fn calculate_total_price(item_count: u32, unit_price: f64) -> f64 {
item_count as f64 * unit_price
}
// 상수 - 대문자 스네이크 케이스
const MAX_CONNECTIONS: u32 = 1000;
const DEFAULT_TIMEOUT: u64 = 30;
// 타입 - 파스칼 케이스 (구조체 예시)
struct UserProfile {
user_id: u64,
display_name: String,
}
설명
이것이 하는 일: Rust의 네이밍 컨벤션은 코드의 일관성과 가독성을 보장하며, 변수의 종류를 이름만으로도 파악할 수 있게 해줍니다. 첫 번째로, 변수와 함수는 소문자로 시작하고 단어 사이를 언더스코어로 연결합니다.
user_name, total_count, is_valid처럼요. 이것은 Python이나 Ruby와 비슷한 스타일입니다.
컴파일러는 카멜 케이스(userName)를 사용하면 경고를 발생시킵니다. 이 경고를 무시할 수도 있지만, 커뮤니티 표준을 따르는 것이 좋습니다.
그 다음으로, 함수 이름도 같은 규칙을 따릅니다. calculate_total_price처럼 동사로 시작하고 스네이크 케이스를 사용합니다.
파라미터 이름도 마찬가지입니다. item_count, unit_price처럼 의미를 명확히 전달하는 이름을 사용하세요.
줄임말(cnt, usr)보다는 완전한 단어를 사용하는 것이 좋습니다. 코드는 한 번 작성되지만 여러 번 읽히기 때문입니다.
세 번째로, 상수는 모두 대문자로 작성하고 단어 사이를 언더스코어로 연결합니다. MAX_CONNECTIONS, DEFAULT_TIMEOUT처럼요.
이렇게 하면 코드를 읽을 때 "이것은 상수이고, 변하지 않는 값이구나"라고 즉시 알 수 있습니다. C나 C++의 관행과 비슷합니다.
네 번째로, 타입(구조체, 열거형, 트레이트)은 파스칼 케이스를 사용합니다. UserProfile, HttpResponse, ToString처럼 각 단어의 첫 글자를 대문자로 쓰고 언더스코어 없이 연결합니다.
이것은 Java나 C#과 비슷한 스타일입니다. 구조체의 필드는 다시 스네이크 케이스를 사용합니다.
user_id, display_name처럼요. 다섯 번째로, 의미 있는 이름을 사용하는 것이 중요합니다.
x, y, z 같은 한 글자 변수는 루프 인덱스나 수학 좌표처럼 관례적인 경우에만 사용하세요. 대부분의 경우 temp, data, info 같은 일반적인 이름보다는 user_email, order_status, connection_pool처럼 구체적인 이름이 낫습니다.
여러분이 Rust의 네이밍 컨벤션을 따르면, 커뮤니티 표준에 맞는 코드를 작성하게 되고, 다른 개발자들과 협업하기 쉬워지며, 오픈소스 프로젝트에 기여할 때도 자연스럽게 받아들여집니다.
실전 팁
💡 rustfmt 도구를 사용하면 자동으로 네이밍을 포함한 코드 스타일을 맞춰줍니다. cargo fmt 명령어로 실행하세요.
💡 clippy는 더 강력한 린터로, 네이밍뿐만 아니라 코드 품질 전반을 검사합니다. cargo clippy로 실행하세요.
💡 약어를 사용할 때는 조심하세요. HTTP, URL 같은 잘 알려진 약어는 괜찮지만, 팀 내부에서만 통하는 약어는 피하세요.
💡 불리언 변수는 is_, has_, should_ 같은 접두사를 붙이면 의미가 명확해집니다. is_active, has_permission, should_retry처럼요.
💡 컬렉션 변수는 복수형을 사용하세요. users, items, connections처럼요. 단수형은 개별 요소에 사용합니다.