[러스트] 불변성
모든 프로그래밍 언어 대부분이 가진 개념을 러스트가 어떻게 다루는지 알아본다.
- 변수, 기본 타입들, 함수, 주석, 제어문 등
변수와 가변성
기본 변수는 불변성 즉, 변하지 않는다. => 안전성과 손쉬운 동시성을 가질 수 있음
변수가 불변인 경우, 값이 이름에 bind 되면 해당 값을 변경할 수 없다.
아래 처럼 variables 디렉터리를 생성해 확인해보자.
cargo new --bin variables
src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
//
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
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to previous error
에러가 나는데, 이는 불변성 변수 x에 재할당을 했기 때문이다.
러스트는 변수의 불변성을 보장함으로써 변수의 값이 변경되어 버그가 일어나는 경우(고통의 트래킹!)를 방지함으로써 코드를 더 합리적으로 만들어준다.
그러나 러스트는 가변성의 유용성 역시 놓치지 않았다.
바로, mut 을 사용함으로써 변수를 가변적으로 만드는 것이다.
mut 은 x에 bind된 값을 변경할 수 있게 만든다.
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.30 secs
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
가변 변수가 유용한 경우는
대규모 데이터 구조체를 다루는 경우이다. -> 가변 인스턴스를 사용하는 것이 새로 인스턴스를 할당하고 반환하는 것보다 빠를 수 있음
그러나, 데이터 규모가 작을수록 새 인스턴스를 생성하고 함수적 프로그래밍 스타일로 작성하는 것이 더 합리적이고, 약간의 성능 하락을 통해 가독성을 확보할 수 있다면 그것이 더 가치있을 수 있다.
언뜻 불변성의 변수는 상수(constant)와 비슷해보이지만 상수는 mut 을 허용하지 않는다.
상수는 let 대신 const 키워드를 사용하고 값의 타입을 함께 선언해야한다.
상수는 오직 상수 표현식만 설정될 수 있으며 함수 호출의 결과값이나 그 외 런타임에 결정되는 값으로 설정될 수 없다.
다음의 예제처럼 상수 (MAX_POINTS)는 값의 타입과 함께 선언한다.
const MAX_POINTS: u32 = 100_000;
상수는 선언된 영역 내에서 항상 유효하므로 유용하다. (한 게임에서 획득할 수 있는 최대 포인트 등)
Shadowing
let으로 이전에 선언한 변수와 동명의 새 변수를 선언할 수 있고,
새 변수는 이전 변수를 shadow 한다. 이를 첫 변수가 두번째 변수에 의해 shadowed 되었다고 표현한다.
-> 해당 변수명이 두번째 변수의 값을 갖게 된다는 의미
아래의 예제와 같이 활용할 수 있다.
fn main() {
let x = 5; // x <- 5
let x = x + 1; // x <- 6
let x = x * 2; // x <- 12
println!("The value of x is: {}", x);
}
이는 mut 으로 선언한 것과는 차이가 있는데, let 없이 변수에 새 값을 대입하려고 하면 컴파일 에러가 발생하기 때문.
mut 과 let 의 차이는 let 은 효과적으로 동명의 새 변수를 선언하고 값 타입을 변경할 수 있다는 것이다. (새롭게 정의된 변수이기 때문)
아래 예제에서 spaces 는 처음에 string 값을 가지지만 두번째 라인에서 정수 값을 가진다.
let spaces = " ";
let spaces = spaces.len();
이를 아래처럼 mut을 사용하면, 가변 변수의 값의 타입을 변경하게 되어 컴파일 에러가 발생한다.
let mut spaces = " ";
spaces = spaces.len();