Rust에서 variable binding(변수 바인딩)은 값을 변수 이름에 연결하는 과정을 의미하며, Rust 프로그래밍에서 변수 선언과 관련된 기본 개념입니다. Rust는 정적 타입 언어이고 기본적으로 변수가 불변(immutable)으로 선언되기에, 가변성을 가지려면 일부러 명시해야 합니다. 이와 함께 Rust의 바인딩은 shadowing(변수 가리기)이나 스코프와 밀접하게 연관되어 있어, 변수 활용 시 중요한 개념입니다.
Ⅰ. 기본 자료형(Primitive Type)
1. 변수 바인딩 기본
Rust에서 변수 바인딩은 let 키워드로 선언합니다. 이때 변수는 기본적으로 불변입니다. 따라서 선언 후 값을 변경하려 하면 컴파일 에러가 발생합니다.
fn main() { let x = 5; println!("x의 값: {}", x); // x = 6; // 오류: 불변 변수에 재할당 불가 }
이 코드는 x를 5로 바인딩했지만, 이후 x = 6과 같이 변경하려 하면 컴파일 에러가 발생합니다. Rust 컴파일러는 이런 불변 변수 변경 시도를 막아 안정성을 보장합니다.
2. 가변 변수 (Mutable Binding)
변수를 변경 가능하게 하려면 mut 키워드를 사용해 가변 변수로 선언해야 합니다.
fn main() { let mut x = 5; println!("초기 x: {}", x); x = 6; println!("변경된 x: {}", x); }
이렇게 하면 x에 대한 값 변경이 가능해집니다. 컴파일러가 에러 발생 시 mut를 추가하라고 친절히 안내하는 점도 Rust의 특징입니다.
3. 타입 추론 및 명시적 타입 선언
Rust 컴파일러는 대부분의 경우 변수 선언 시 타입을 자동 추론합니다.
let x = 42; // i32로 추론 let y = true; // bool로 추론 let z: u32 = 10; // 명시적 타입 선언
필요에 따라 타입을 명시할 수도 있지만, 대부분 타입 추론에 맡깁니다.
4. 변수 가리기 (Shadowing)
Rust의 독특한 특징인 변수 가리기는 같은 이름의 변수를 같은 스코프 내에서 다시 let으로 선언하여 새 변수를 만드는 것입니다. 이때 이전 변수는 새 변수에 의해 가려집니다.
fn main() { let x = 5; let x = x + 1; // 이전 x를 가리고 새 x 선언 { let x = x * 2; println!("내부 스코프의 x: {}", x); // 12 } println!("외부 스코프의 x: {}", x); // 6 }
위 예제에서 첫 번째 x는 5, 두 번째는 6, 내부 스코프의 x는 12입니다. 이렇게 하면 가변 변수 없이도 값 변경 효과를 낼 수 있고 타입도 변경할 수 있습니다.
5. 스코프와 변수 유효 범위
변수는 선언된 블록 {} 내에서만 유효하며, 블록을 벗어나면 사라집니다.
fn main() { let x = 1; { let y = 2; println!("내부 스코프 y: {}", y); // 2 } // println!("외부 스코프 y: {}", y); // 오류: y는 유효하지 않음 }
또한 내부 스코프에서 같은 이름으로 변수를 다시 선언하면 외부 변수를 가립니다.
6. 실습 예제 종합
fn main() { // 불변 변수 let a = 10; println!("a: {}", a); // a = 20; // 컴파일 에러!
// 가변 변수 let mut b = 10; println!("b 초기값: {}", b); b = 20; println!("b 변경 후: {}", b);
// 변수 가리기 (shadowing) let c = 5; let c = c + 10; // 이전 c 가리기, 이제 c = 15 println!("c: {}", c);
{ let c = c * 2; // 내부 스코프 가리기, c = 30 println!("내부 블록의 c: {}", c); }
println!("외부 블록의 c: {}", c);
// 타입 변경이 가능한 shadowing let d = "문자열"; println!("d는 문자열: {}", d); let d = d.len(); // 같은 이름 변수에 정수 대입 (shadowing) println!("d는 문자열 길이: {}", d); }
출력 결과:
a: 10 b 초기값: 10 b 변경 후: 20 c: 15 내부 블록의 c: 30 외부 블록의 c: 15 d는 문자열: 문자열 d는 문자열 길이: 6
Ⅱ. 벡터(Vector)와 튜플(Tuple) 타입 변수 바인딩
1. 벡터(Vector) 타입 변수 바인딩
벡터는 같은 타입 요소들을 동적 크기로 저장하는 컬렉션입니다. Rust에서 벡터는 Vec<T> 타입으로 표현됩니다.
벡터 바인딩 기본 예:
fn main() { // 빈 vector 생성 (타입 명시 필요) let mut v: Vec<i32> = Vec::new(); // 값 추가 가능하려면 mut 필요 v.push(1); v.push(2); println!("{:?}", v); // 출력: [1, 2]
// 초기값과 함께 벡터 생성 (타입 추론 가능) let v2 = vec![10, 20, 30]; println!("{:?}", v2); // 출력: [10, 20, 30] }
벡터의 요소 접근:
let first = v2[0]; // 인덱스를 통한 접근 (주의: 범위 초과 시 패닉) let maybe_first = v2.get(0); // Option 타입 반환 (안전 접근)
가변 벡터 요소 수정도 mut으로 가능:
let mut v3 = vec![1, 2, 3]; v3[1] = 5; // 두 번째 요소를 5로 변경
벡터는 가변성을 가지고 가리키는 데이터가 동적으로 바뀔 수 있으므로 보통 mut 바인딩과 함께 선언합니다.
2. 3차원 벡터
1) 가변 크기: Vec<Vec<Vec<T>>>
// 2 x 3 x 4 크기의 0으로 채워진 3차원 벡터 let x = 2; let y = 3; let z = 4; let mut v: Vec<Vec<Vec<i32>>> = vec![vec![vec![0; z]; y]; x];
// 값 접근 let team_name = String::from("Blue"); let score = scores.get(&team_name).copied().unwrap_or(0);
println!("Blue 팀 점수: {}", score);
// 해시맵의 모든 키-값 쌍 출력 for (key, value) in &scores { println!("{key}: {value}"); } }
여기서 scores 변수에 해시맵 데이터 구조가 바인딩되었고,
mut 키워드 때문에 값 삽입과 변경이 가능합니다.
.insert() 메서드로 키-값 쌍을 추가하거나 갱신하며,
.get() 메서드로 해당 키에 연관된 값을 읽을 수 있습니다.
요약하면, 해시맵도 배열, 벡터, 튜플, 구조체와 같이 Rust에서 변수 바인딩 개념이 동일하게 적용됩니다. let으로 이름을 데이터에 바인딩하고, 변경하려면 mut가 필수입니다. 해시맵은 키-값 쌍 컬렉션이라는 점에서 특수하지만, 바인딩이라는 개념 면에서는 일반 변수와 차이가 없습니다.
추가적으로 해시맵 내부에 구조체 같은 복합 데이터 타입을 저장해도, 변수 바인딩과 관련된 기본 원칙은 같습니다.
Ⅴ. 사용자 정의 타입
Rust에서 사용자 정의 타입(예: 구조체와 열거형)에 대한 변수 바인딩은 기본 타입이나 벡터, 튜플 등과 동일하게 let 키워드를 사용하여 값을 변수 이름에 연결하는 것을 의미합니다. 하지만 구조체와 열거형은 내부 필드나 variant에 데이터를 포함할 수 있어서, 변수 바인딩과 활용이 더 다양한 형태로 나타납니다.
아래에서 구조체와 열거형을 사용자 정의 타입의 변수 바인딩 예제로 구체적으로 설명합니다.
1. 구조체(Struct) 변수 바인딩 예제
// 구조체 정의 struct Point { x: i32, y: i32, }
fn main() { // 구조체 인스턴스를 생성하고 변수 p에 바인딩 let p = Point { x: 10, y: 20 };
// 구조체 필드에 접근 println!("x: {}, y: {}", p.x, p.y);
// 가변 바인딩 시 필드 값 변경 가능 let mut p_mut = Point { x: 5, y: 5 }; p_mut.x = 15; println!("변경된 x: {}", p_mut.x);
// 구조체를 분해하여 필드 값을 각각 변수에 바인딩 let Point { x: a, y: b } = p; println!("분해된 x: {}, y: {}", a, b); }
let p = Point { … };에서 p는 Point 타입 값에 변수 바인딩입니다.
패턴 매칭처럼 let Point { x: a, y: b } = p; 구문으로 구조체 필드를 변수 a, b에 바인딩할 수도 있습니다.
mut 키워드로 가변 바인딩을 선언하면 구조체 필드를 수정할 수 있습니다.
2. 열거형(Enum) 변수 바인딩 예제
// 열거형 정의 enum Message { Quit, // 데이터 없는 variant Move { x: i32, y: i32 }, // 필드가 있는 variant (struct-like) Write(String), // 튜플 형태 variant ChangeColor(i32, i32, i32), // 여러 필드를 가진 튜플 variant }
fn main() { // enum 값에 변수 바인딩 let msg = Message::Move { x: 10, y: 20 };
// match로 variant별 데이터 바인딩과 처리 match msg { Message::Quit => println!("Quit variant"), Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), Message::Write(text) => println!("Write message: {}", text), Message::ChangeColor(r, g, b) => println!("Change color to {}, {}, {}", r, g, b), } }