변수와 가변성

Beginner

This tutorial is from open-source community. Access the source code

소개

**변수와 가변성 (Mutability)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 실력을 연습할 수 있습니다.

이 랩에서는 Rust 의 가변성 개념을 탐구합니다. 변수가 기본적으로 불변 (immutable) 이며, mut 키워드를 사용하여 가변 (mutable) 하게 만드는 방법, 안전성과 동시성을 위해 불변성이 얼마나 중요한지 논의하고, 특정 상황에서 가변성의 유용성을 강조합니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 87%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

변수와 가변성 (Mutability)

"변수로 값 저장하기"에서 언급했듯이, 기본적으로 변수는 불변 (immutable) 입니다. 이는 Rust 가 안전성과 Rust 가 제공하는 쉬운 동시성을 활용할 수 있도록 코드를 작성하도록 유도하는 많은 방법 중 하나입니다. 하지만, 여전히 변수를 가변 (mutable) 하게 만들 수 있는 옵션이 있습니다. Rust 가 왜 불변성을 선호하도록 권장하는지, 그리고 때로는 왜 이를 벗어나고 싶을 수 있는지 살펴보겠습니다.

변수가 불변일 때, 값이 이름에 바인딩되면 해당 값을 변경할 수 없습니다. 이를 설명하기 위해, cargo new variables를 사용하여 project 디렉토리에 variables라는 새 프로젝트를 생성합니다.

그런 다음, 새 variables 디렉토리에서 src/main.rs를 열고 코드를 다음 코드로 바꿉니다. 아직 컴파일되지 않습니다.

파일 이름: src/main.rs

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

cargo run을 사용하여 프로그램을 저장하고 실행합니다. 다음 출력과 같이 불변성 오류와 관련된 오류 메시지를 받게 됩니다.

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

이 예제는 컴파일러가 프로그램에서 오류를 찾는 데 어떻게 도움이 되는지 보여줍니다. 컴파일러 오류는 좌절감을 줄 수 있지만, 실제로 이는 프로그램이 원하는 대로 안전하게 작동하지 않는다는 의미일 뿐입니다. 이는 여러분이 훌륭한 프로그래머가 아니라는 의미가 아닙니다! 숙련된 Rust 개발자 (Rustacean) 도 여전히 컴파일러 오류를 겪습니다.

x 불변 변수에 두 번째 값을 할당하려고 시도했기 때문에 cannot assign twice to immutable variablex`` 오류 메시지를 받았습니다.

불변으로 지정된 값을 변경하려고 시도할 때 컴파일 시간 오류가 발생하는 것은 중요합니다. 이러한 상황이 버그로 이어질 수 있기 때문입니다. 코드의 한 부분이 값이 절대 변경되지 않는다는 가정하에 작동하고, 코드의 다른 부분이 해당 값을 변경하는 경우, 코드의 첫 번째 부분이 설계된 대로 작동하지 않을 수 있습니다. 이러한 종류의 버그의 원인은, 특히 두 번째 코드 조각이 값을 때때로 변경하는 경우, 사후에 추적하기 어려울 수 있습니다. Rust 컴파일러는 값이 변경되지 않는다고 선언하면 실제로 변경되지 않도록 보장하므로, 직접 추적할 필요가 없습니다. 따라서 코드를 더 쉽게 추론할 수 있습니다.

하지만 가변성은 매우 유용할 수 있으며, 코드를 더 편리하게 작성할 수 있게 해줍니다. 변수는 기본적으로 불변이지만, 2 장에서 했던 것처럼 변수 이름 앞에 mut를 추가하여 가변하게 만들 수 있습니다. mut를 추가하면 코드의 미래 독자에게 이 변수의 값을 변경할 것임을 나타내어 의도를 전달합니다.

예를 들어, src/main.rs를 다음과 같이 변경해 보겠습니다.

파일 이름: src/main.rs

fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

이제 프로그램을 실행하면 다음과 같은 결과를 얻습니다.

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

mut를 사용하면 x에 바인딩된 값을 5에서 6으로 변경할 수 있습니다. 궁극적으로, 가변성을 사용할지 여부를 결정하는 것은 여러분에게 달려 있으며, 특정 상황에서 무엇이 가장 명확하다고 생각하는지에 따라 달라집니다.

상수 (Constants)

불변 변수와 마찬가지로, *상수 (constants)*는 이름에 바인딩되고 변경할 수 없는 값입니다. 하지만 상수와 변수 사이에는 몇 가지 차이점이 있습니다.

첫째, 상수에는 mut를 사용할 수 없습니다. 상수는 기본적으로 불변일 뿐만 아니라 항상 불변입니다. let 키워드 대신 const 키워드를 사용하여 상수를 선언하며, 값의 타입은 반드시 주석 처리해야 합니다. "데이터 타입"에서 타입과 타입 주석에 대해 다룰 것이므로, 지금은 세부 사항에 대해 걱정하지 마십시오. 항상 타입을 주석 처리해야 한다는 것만 알아두세요.

상수는 전역 범위 (global scope) 를 포함하여 모든 범위에서 선언할 수 있으며, 이는 코드의 여러 부분에서 알아야 할 값에 유용하게 사용됩니다.

마지막 차이점은 상수는 런타임에만 계산할 수 있는 값의 결과가 아닌, 상수 표현식 (constant expression) 으로만 설정할 수 있다는 것입니다.

다음은 상수 선언의 예입니다.

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

상수의 이름은 THREE_HOURS_IN_SECONDS이고, 값은 60(1 분당 초 수) 에 60(1 시간당 분 수) 을 곱하고 3(이 프로그램에서 계산하려는 시간 수) 을 곱한 결과로 설정됩니다. 상수에 대한 Rust 의 명명 규칙은 단어 사이에 밑줄을 사용하여 모두 대문자를 사용하는 것입니다. 컴파일러는 컴파일 시간에 제한된 일련의 연산을 평가할 수 있으므로, 이 상수를 10,800 값으로 설정하는 대신, 이해하고 확인하기 쉬운 방식으로 이 값을 작성할 수 있습니다. 상수를 선언할 때 사용할 수 있는 연산에 대한 자세한 내용은 Rust Reference 의 상수 평가 섹션 ( https://doc.rust-lang.org/reference/const_eval.html ) 을 참조하십시오.

상수는 선언된 범위 내에서 프로그램이 실행되는 전체 시간 동안 유효합니다. 이러한 속성은 게임의 플레이어가 얻을 수 있는 최대 점수 또는 빛의 속도와 같이 프로그램의 여러 부분에서 알아야 할 애플리케이션 도메인의 값에 유용합니다.

프로그램 전체에서 사용되는 하드코딩된 값을 상수로 명명하는 것은 코드의 미래 유지 관리자에게 해당 값의 의미를 전달하는 데 유용합니다. 또한 하드코딩된 값을 나중에 업데이트해야 하는 경우 코드에서 변경해야 하는 위치가 하나만 있다는 것도 도움이 됩니다.

섀도잉 (Shadowing)

2 장의 추측 게임 튜토리얼에서 보았듯이, 이전 변수와 동일한 이름으로 새 변수를 선언할 수 있습니다. Rust 개발자들은 첫 번째 변수가 두 번째 변수에 의해 *섀도잉 (shadowed)*된다고 말합니다. 이는 변수의 이름을 사용할 때 컴파일러가 두 번째 변수를 보게 된다는 의미입니다. 실제로, 두 번째 변수는 첫 번째 변수를 가리고, 해당 변수 이름의 모든 사용을 자체적으로 가져가며, 자체적으로 섀도잉되거나 범위가 종료될 때까지 유지됩니다. 다음과 같이 동일한 변수 이름을 사용하고 let 키워드를 반복하여 변수를 섀도잉할 수 있습니다.

파일 이름: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}

이 프로그램은 먼저 x를 값 5에 바인딩합니다. 그런 다음 let x =를 반복하여 새 변수 x를 생성하고, 원래 값을 가져와 1을 더하여 x의 값이 6이 됩니다. 그런 다음 중괄호로 생성된 내부 범위 내에서 세 번째 let 문도 x를 섀도잉하고 새 변수를 생성하여 이전 값에 2를 곱하여 x의 값을 12로 만듭니다. 해당 범위가 끝나면 내부 섀도잉이 종료되고 x는 다시 6이 됩니다. 이 프로그램을 실행하면 다음과 같은 출력이 표시됩니다.

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

섀도잉은 변수를 mut로 표시하는 것과 다릅니다. let 키워드를 사용하지 않고 이 변수에 실수로 다시 할당하려고 하면 컴파일 시간 오류가 발생하기 때문입니다. let을 사용하면 값에 대해 몇 가지 변환을 수행할 수 있지만, 해당 변환이 완료된 후에는 변수가 불변으로 유지됩니다.

mut와 섀도잉의 또 다른 차이점은 let 키워드를 다시 사용할 때 효과적으로 새 변수를 생성하기 때문에 값의 타입을 변경할 수 있지만 동일한 이름을 재사용할 수 있다는 것입니다. 예를 들어, 프로그램이 사용자에게 텍스트 사이에 원하는 공백 수를 공백 문자를 입력하여 표시하도록 요청한 다음 해당 입력을 숫자로 저장하려는 경우를 생각해 보겠습니다.

let spaces = "   ";
let spaces = spaces.len();

첫 번째 spaces 변수는 문자열 타입이고, 두 번째 spaces 변수는 숫자 타입입니다. 따라서 섀도잉은 spaces_strspaces_num과 같은 다른 이름을 생각해내야 하는 수고를 덜어줍니다. 대신, 더 간단한 spaces 이름을 재사용할 수 있습니다. 그러나 여기에 표시된 것처럼 이 작업을 위해 mut를 사용하려고 하면 컴파일 시간 오류가 발생합니다.

let mut spaces = "   ";
spaces = spaces.len();

오류는 변수의 타입을 변경할 수 없다고 말합니다.

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

이제 변수가 작동하는 방식을 살펴보았으므로, 변수가 가질 수 있는 더 많은 데이터 타입을 살펴보겠습니다.

요약

축하합니다! 변수와 가변성 (Mutability) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.