이번 시간부터는 Rust에 보편적인 개념을
배워보도록 하겠습니다.
변수와 가변성
Rust에서 기본 변수는 불변성입니다.
이것은 Rust 가 제공하는 안전성과 손쉬운
동시성이라는 장점을 취할 수 있게
코드를 작성하게끔 강제하는 요소중 하나입니다.
하지만 여전히 가변변수를 사용하고 싶을텐데
어떻게 그리고 왜 Rust가 불변성을 애호하길
권장하는지 알아보면 그런 생각을 포기할 수 있을
것 입니다.
변수가 불변성일 경우 일단 값이 이름에 bound되면
해당 값을 변경할 수 있는데
시험 삼아서 variables 라는 프로젝트를 만들어
보겠습니다.
cargo new --bin variables
Filename: 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 명령어를
통해서 실행해 보면 다음과 같은 에러가 출력됩니다.
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
위 예제는 컴파일러가 당신이 만든 프로그램에서
당신을 도와 에러를 찾아주는 방법에 대해
보여주고 있습니다.
에러가 나타내는 것은 불변성 변수에 재할당이며,
원인은 우리가 불변성 변수 x에 두 번째로
값을 할당했기 때문입니다.
우리가 이전에 불변성으로 선언한 것의 값을
다시 변경하고자 하는 시도를 하면 컴파일
타임의 에러를 얻게 되고
이로 인해서 버그가 발생할 수 있기 때문에
이점이 가장 중요합니다.
만약 우리 코드의 일부는 값이 변경되지
않는다는 것을 가정하고 다른 코드는
이와 다른게 값을 변경한다면
전자에 해당하는 코드는 우리가 의도한
대로 수행되지 않을 수 있습니다.
특히 후자에 해당되는 코드가 항상 그렇지
않고 가끔 값을 변경하는 경우 나중에
버그의 원인을 추적하기가 상당히
까다롭고 어렵습니다.
Rust에서는 컴파일러가 변경되지 않는 값에
대한 보증을 해주고, 실제로 이는 바뀌지 않고
이것이 의미하는 바는 당신이 코드를
작성하거나 분석할 시 변수의 값이 어떠헥
변경되는 가를 추적할 필요가 없기에
코드를 더 합리적으로 만들어줍니다.
하지만 가변성이 매우 유용하게 사용될 수도
있습니다. 변수는 기본적으로 불변성이지만
우리는 변수명의 접두어로 mut를 추가하는
것을 통해서 가변성 변수를 선언할 수 있습니다.
이 변수의 값이 변경을 허용하는 것에 추가로
향후 코드를 보는 사람에게 코드의
다른 부분에서 해당 변수의 값을 변경할 것이라는
의도를 보여줍니다.
예를 들어 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 (/home/koras02/study/rust/projects/hello_world/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
mut를 사용하여, x에 bind된 값을
5에서 6으로 변경할 수 있습니다.
불변성 변수만을 사용하는 것 보다
가변성 변수를 사용하여 보다 쉽게
구현할 수 있을 경우 가변성 변수를
만들어 사용할 수 있습니다.
이런 의사 결정에 있어 버그를 예방하는 것
이외 고려해야할 요소들이 있는데
예를 들어 대규모 데이터 구조체를
다루는 경우 가변한 인스턴스를
사용하는 것이 새로 인스턴스를 할당하고
반환하는 것보다 빠를 수 있습니다.
데이터 규모가 작을 수록 새 인스턴스를
생성하고 함수적 프로그래밍 스타일로
작성하는 것이 더 함리적이며
그렇기에 약간의 성능 하락을 통해 가독성을
확보할 수 있다면 더 가치있는 선택입니다.
변수와 상수간의 차이점들
변수의 값을 변경할 수 없다는 사항이
아마 다른 언어가 가진 프로그램 개념을
떠오르게 할텐데
상수 불변성 변수와 마찬가지로
상수 또한 이름으로 bound된 후에는 값의
변경이 허용되지 않지만
상수와 변수는 조금 다릅니다.
첫째, 상수에 대해 mut를 사용하는 것이
허용되지 않습니다
상수는 기본 설정이 불변성인 것이 아니고
불변성 그 자체 입니다.
우리가 상수를 사용하고자 하면
let 키워드 대신 const 키워드를 사용해야 하고
값의 유형을 선언해야 합니다.
상수는 전체 영역을 포함해 어떠 영역에서도
선언될 수 있습니다.
이는 코드의 많은 부분에서 사용될 필요가 있는 값을
다루는데 유용합니다.
마지막 차이점은 상수는 오직 상수 표현식만
설정될 수 있지, 함수 호출의 결과값이나
그 외에 실행 시간에 결정되는 값이
설정될 수는 없다는 점입니다.
아래 MAX_POINTS라는 이름을 갖는
상수를 선언하는 예제에서는 값을 100,000으로
설정합니다.
const MAX_POINTS: u32 = 100_000;
상수는 자신이 선언되어 있는 영역 내에서
프로그램이 실행되는 시간 동안
항상 유효하기에, 당신의 어플리케이션
도메인 전체에 걸쳐 프로그램의 다양한 곳에서
사용되는 값을 상수로 하면 유용합니다.
사용자가 한 게임에서 흭득할 수 있는 최대 포인트
빛의 속도 값 등등...
당신이 프로그램 전체에 걸쳐 하드코드 해야하는
값들이 이름지어 상수로 사용하면
향후 코드를 유지보수 하게 될 사람에게
그 의미를 전달할 수 있기에 유용합니다.
또한 향후 해당 값을 변경해야 하는 경우
상수로 선언된 값 한 곳만 변경하면
되므로 도움이 될 것 입니다.
Shadowing
이전에 선언한 변수와 같은 이름의 새 변수를
선언 할 수 있고, 이전 변수를 shadows하게 됩니다.
Rustaceans들은 이를 첫 변수가
두 번째에 의해 shadowed 됐다고 표현하게 됩니다.
해당 변수명은 두 번째 변수의
값을 갖게 된다는 뜻이죠,
let 키워드를 사용해 다음처럼
반복하여 같은 변수명으로 변수를 shadow 할 수 있습니다.
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x)
}
이 프로그램은 x에 값 5를 bind 합니다.
이후 반복된 let x = 구문으로 x를
shadow하고 원본 값 1을 더해
x의 값은 6이 됩니다.
세변째 let문으로 또 x를 shadow하고
이전 값에 2를 곱해 x의 최종값은
12가 됩니다.
이 프로그램을 실행하면 다음과 같은
결과를 볼 수 있습니다.
$ cargo run
Compiling variables v0.1.0 (/home/koras02/study/rust/projects/hello_world/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/variables`
The value of x is: 12
이와 같은 사용은 변수를 mut로 선언하는 것과는
차이가 있게 됩니다.
그 이유는 let키워드를 사용하지 않고
변수에 새로 값을 대입하려고 하면
컴파일 시 에러를 얻기 떄문입니다.
우리가 몇변 값을 변경할 수 있지만
그 이후 변수는 불변성을 가지게 됩니다.
또 다른 mut와 shadowing의 차이는
let키워드를 다시 사용하여
효과적으로 새 변수를 선언하고
값의 유형을 변경할 수 있으면서도
동일 이름을 사용할 수 있다는 점 입니다.
예를 들어 공백 문자들을 입력받아
얼마나 많은 공백 문자가 있는지 보여주고자
할 때, 실제로는 저장하고자 하는 것은
공백의 개수일테죠.
let spaces = " ";
let spaces = spaces.len();
이와 같은 구조가 허용되는 이유로
첫 spaces 변수가 문자열 유형이고
두 번째는 첫 번째 것과 동일한 이름을
가진 새롭게 정의된 숫자 유형의
변수 이기 때문입니다.
Shadowing은 space_str이나
space_num과 같이 대체된 이름을
사용하는 대신 spaces 이름을 사용할 수
있게 해줍니다.
그러나 우리가 mut를 사용하면
let mut spaces = " ";
spaces = spaces.len();
우리는 다음처럼 변수의 유형을 변경할 수 없다는
컴파일-시의 에러를 얻게 됩니다.
error[E0308]: mismatched types
--> src/main.rs:32:10
|
31 | let mut spaces = " ";
| ----- expected due to this value
32 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
참고 자료
'백엔드 > Rust' 카테고리의 다른 글
[Rust] Rust 개념 - 1.보편적인 프로그래밍 개념 - 데이터 타입들 (0) | 2023.02.12 |
---|---|
[Rust] Rust 추리 게임 만들기 (0) | 2023.01.11 |
[RUST] RUST 설치하기 (0) | 2023.01.10 |