개요
Rust 에서 vec! 으로 만든 배열의 값들을 셔플하는 간단한 예제를 만들어보아요. rust 가 너무 헷갈려서 이 쉬워보이는 연습도 힘겹게 했네요.
미리 알고 있으면 좋은 것들
let
vslet mut
: 변수를 선언할 때에는 기본적으로 불변형(immutable) 입니다.mut
키워드를 뒤에 붙이면 변형을 할 수 있습니다.- 슬라이스: 그냥 배열같은 것의 일부분의 레퍼런스 같은 것이라고 이해하고 있습니다.
- Range: 반복적인 동작을 정형화한 어떤 것이라고 이해하고 있습니다. 이터레이터랑 비슷한 것.
(0..5)
라면 0이상 5미만의 어떤 반복적인 행동을 하겠다는 맥락을 만드는 것입니다.
랜덤 수 만들기
rand 라는 크레이트를 사용합니다. 필요한 개념은 많지 않습니다.
코드 및 결과
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
const LEN: usize = 30;
let mut arr: Vec<_> = (0..LEN).collect();
println!("before: {:?}", arr); //
(0..LEN - 1).for_each(|index| {
let change = rng.gen_range(index + 1..LEN);
arr.swap(index, change);
});
println!("after: {:?}", arr);
}
아래는 그 결과입니다.
before: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
after: [21, 27, 24, 15, 29, 9, 26, 6, 0, 11, 4, 2, 8, 10, 25, 23, 14, 7, 20, 17, 16, 3, 12, 19, 13, 22, 5, 28, 18, 1]
코드 설명
일단 thread_rng
를 통해 랜덤 숫자 생성기를 만듭니다. 랜덤 숫자도 사실 어떤 계산된 값인데 무작위로 보이는 것처럼 할 뿐이고, 그 계산을 담당하는 객체입니다.
길이는 30
으로 그냥 두었습니다. Range
의 collect
를 이용하여 배열을 생성해줍니다. 그 다음 index
를 0
부터 LEN - 1
미만까지 반복하며 해당 인덱스 이후의 나머지 부분의 랜덤한 위치와 swap
해줍니다. 원리는 간단하고 코드도 그렇게 길지는 않지만 여전히 불변성이 몸에 익지 않네요. 허허..
후기
immutable let
과 const
의 기능상 유사점 때문에 헷갈림
const
는 컴파일-타임에 값으로써 모조리 변환됩니다. 마치 C 에서의#define
과 같은 효과라고 보면 될 것 같습니다.let
은 실제 런타임 때 메모리에 값이 저장됩니다.let
은mut
키워드와 함께 있어야 변수 수정이 가능합니다.mut
키워드가 없더라도 쉐도잉(Shadowing)을 통해 변수의 값을 변경할 수 있습니다. 그 외 쓰임새에서 이런저런 차이가 있습니다.- 그래서 드는 의문점: 컴파일러가
let mut
이 아닌let
으로 선언된 immutable 변수의 쉐도잉 여부를 판단하여, 만약 쉐도잉이 되지 않았다면 모조리 const 처럼 컴파일타임에 상수로 변경해버릴 수 있는 건 아닌가요? 하여튼 기능상의 유사점 때문에 어느 상황에서immutable let
변수를 쓸지, 아니면const
상수를 쓸지 감이 잘 안옵니다. - 아하.. 만약 런타임 중에 새로운 값을 생성할 필요가 있는데, (예를 들어 표준 입력으로 값을 받는다던가) 그 값이 생성 이후 바뀌지 않아야 한다는 맥락을 만들기 위해서 immutable let 이 필요한 것이군요! 이제 깨달음을 얻었습니다.
- 참고: 그나마 궁금증이 해소된 StackOverflow – What is the difference between immutable and const variables in Rust?
뭔가 반복해야 하는 것은 이터레이터를 적극적으로 활용하자
자바스크립트에서 map
, filter
, foreach
등을 적극적으로 활용했었는데, rust에서도 비슷한 개념이 있습니다. 어떤 컨테이너에서 iter()
를 통해서 반복자를 얻을 수 있고, 이것을 이용해 for_each
등의 반복되는 동작을 지정해줄 수 있습니다. 자바스크립트와 다른 점은 반복자의 종류에 따라서 내부의 값을 바꿀 수도 있고, 바꿀 수도 없다는 점입니다. 값을 변형하고 싶다면 iter_mut()
을 활용해야 합니다.
rand 크레이트에 shuffle 기능이 존재함.
이건 꿈에도 몰랐습니다. 하기는 없을 리가 없지요… 내부 소스를 보니까 위와 거의 비슷하게 동작하네요. 아래는 그 내부 소스입니다.
pub trait SliceRandom {
// ...
fn shuffle<R>(&mut self, rng: &mut R)
where R: Rng + ?Sized {
for i in (1..self.len()).rev() {
// invariant: elements with index > i have been locked in place.
self.swap(i, gen_index(rng, i + 1));
}
}
}