메뉴

Rust - Variable and Mutability

2021-12-31 07:00:32

목차

Variables and Mutability

모든 언어들이 변수(Variables)를 다룬다. 하지면 Rust에서는 Mutability라는 새로운 개념을 다룬다. Mutability는 "변할 수 있는"이라는 의미인데, Rust에서 Mutability가 의미하는 바를 짚어야 할 것 같다. 아래 코드를 실행해보자.
fn main() {
    let foo = 5;
    println!("{}",foo);
    foo = 10;
    println!("{}",foo);
}
변수 foo에 5를 대입하고, 다시 10을 대입했다. 특별히 문제 될게 없어 보이는 코드이지만 빌드하면 아래와 같은 에러가 발생한다.
$ cargo run 
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
error[E0384]: cannot assign twice to immutable variable `foo`
 --> src/main.rs:4:5
  |
2 |     let foo = 5;
  |         ---
  |         |
  |         first assignment to `foo`
  |         help: consider making this binding mutable: `mut foo`
3 |     println!("{}",foo);
4 |     foo = 10;
  |     ^^^^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `hello_world` due to previous error
"cannot assign twice to immutable variable" 해석하자면 "불변변수에 두번 할당 할 수 없습니다." 이다. 불변(immutable) 변수에 주목하자.

Rust에서 변수는 다른 언어에서의 변수와 마찬가지로 메모리에 저장된 값을 가리키는 이름이다. 그런데 코드를 보면 foo 변수를 선언할 때 let을 사용했다. let은 Rust의 키워드로 변수에 값을 바인딩한다. 이렇게 바인딩된 변수는 불변성(immutable)을 가지며 값을 변경 할 수 없다.

바인딩된 변수의 값을 바꾸고 싶다면 mut를 이용해서 변경가능한 변수(가변성을 가진 변수)로 만들 수 있다. 예제코드가 컴파일되도록 수정했다.
fn main() {
    let mut foo = 5;
    println!("{}",foo);
    foo = 10;
    println!("{}",foo);
}

불변성이 왜 중요한가 ?

불변성으로 선언된 변수의 값을 바꾸려는 시도를 하면 컴파일 시간에 에러를 받게 된다. 프로그래머는 변수가 불변성을 가지고 있는지 가변성을 가지고 있는지를 명확히 알 수 있으며, 해당 값이 의도치 않게 변경됐을 가능성을 걱정하거나 추적할 필요가 없다.

이는 코드를 효과적으로 관리 할 수 있게 만들어준다.

Variable 선언

Rust에서 새 변수를 만드는 가장 간단한 방법은 "let" 키워드를 사용하는 것이다.
fn main() {
    let my_num = 5;
    println!("{}", my_num);
}

let은 현재 범위(scope)에 새 변수를 도입한다. 이 변수는 기본적으로 불변성을 가진다.

Rust에서 변수는 타입을 설정 할 수 있다. 예제코드의 my_num의 타입을 가져와보자.
fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}
fn main() {
    let my_num = 5;
    print_type_of(&my_num);
}

실행해보자.
$ cargo run
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/hello_world`
i32

Rust에서 따로 타입을 설정하지 않으면, 기본 타입으로 설정된다. "i8"로 타입을 설정해보자.
fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}
fn main() {
    let my_num: i8 = 5;
    print_type_of(&my_num);
}

Variable Shadowing

아래 코드가 컴파일 될지 안될지를 예상해보자.
fn main() {
    let my_num = 32;
    let my_num = my_num+32;
    println!("{}", my_num);
}

실행된다.
$ cargo run
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/hello_world`
64

분명히 rust의 변수는 불변성을 가진다고 했는데, 값이 변경되었다. 그 전에 같은 이름의 변수를 두 번 선언했다.

Rust에서 let을 이용해서 변수를 선언하면, 같은 이름이라고 하더라도 새로 변수를 할당하게 된다. 이름이 같은 경우에는 이를 shadowing 변수라고 한다. 아래 코드를 실행해보자.
fn main() {
    let my_num = 32;
    println!("{:p}", &my_num);

    let my_num = my_num+32;
    println!("{:p}", &my_num);
    println!("{}", my_num);
}

주소 값이 다르게 나오는 걸 확인 할 수 있다. 서로 다른 변수라는 의미다.
0x7ffd8876907c
0x7ffd887690d4
서로 다른 변수이기 때문에 불변성을 해치는게 아니고, 실행하는데 문제가 없다.

서로 다른 변수이기 때문에 타입이 달라져도 상관 없다.
fn main() {
    let my_age = 25;
    let my_age = format!("{} year", my_age); 

    println!("{}",my_age);
}
그렇다면 shadow를 언제 사용하는 이유가 뭘까 ? 위의 코드를 다른 언어로 작성 할 때는 my_age와 함께 포맷팅된 값을 저장하기 위한 my_age_str 같은 변수를 따로 선언해서 사용해야 한다. Rust에서는 shadow를 이용해서 일관성 있는 변수이름의 관리가 가능하다.

Const

상수는 다른 대부분의 언어가 가지고 있는 개념으로 "변경 할 수 없는 값"이다. 불변변수(immutable variables)와 상수는 값을 바꿀 수 없다는 측면은 동일하지만 몇 가지 차이가 있다.

const MAX_AGE: u8 = 150;
fn main() {
    println!("{}", MAX_AGE);

}

실행해보자.
$ cargo run
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/hello_world`
150

혹시라도 상수값을 변경하려고 하면 컴파일 실패한다.
const MAX_AGE: u8 = 150;
fn main() {
    MAX_AGE = 4;
    println!("{}", MAX_AGE);
}

실행해보자.
$ cargo run
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
error[E0070]: invalid left-hand side of assignment
 --> src/main.rs:3:13
  |
3 |     MAX_AGE = 4;
  |     ------- ^
  |     |
  |     cannot assign to this expression

For more information about this error, try `rustc --explain E0070`.

반면 let은 범위(scope)안에서 정의해야 한다. 아래의 코드는 허용되지 않는다.
let MAX_AGE: u8 = 150;
fn main() {
    println!("{}", MAX_AGE);
}

실행해보자.
$ cargo run
   Compiling hello_world v0.1.0 (/home/yundream/workspace/rust/HelloWorld)
error: expected item, found keyword `let`
 --> src/main.rs:1:1
  |
1 | let MAX_AGE: u8 = 150;
  | ^^^ expected item

error: could not compile `hello_world` due to previous error

정리