Rust

Rust 난수 생성 완벽 가이드 - rand 크레이트 사용법

드리프트2 2024. 5. 17. 19:58

이번 글에서는 Rust에서 난수를 생성하는 방법을 쉽게 설명해 드리려고 합니다.

난수는 여러 가지 용도로 사용되는데요, 예를 들어 게임에서 무작위 이벤트를 만들거나, 보안 키를 생성하는 등 다양한 분야에서 활용됩니다.

그럼, 어떻게 Rust에서 난수를 생성할 수 있는지 알아볼까요?

의사 난수 생성기 (Pseudo Random Number Generator, PRNG)

먼저, 의사 난수 생성기(PRNG)에 대해 간단히 설명드리겠습니다.

의사 난수는 난수처럼 보이지만, 사실은 특정 알고리즘에 따라 생성된 수열입니다.

다시 말해, 일정한 규칙에 따라 생성되기 때문에 사실상 예측이 가능합니다.

반면에, 물리적 난수는 열 소음이나 우주선 등 예측 불가능한 현상에 의해 생성됩니다.

주기

PRNG는 결정론적이기 때문에, 결국에는 같은 수열이 반복되게 됩니다.

이를 주기(period)라고 합니다. 예를 들어, N개의 비트로 표현되는 PRNG는 최대 (2^N)개의 고유한 상태를 가질 수 있습니다.

따라서 충분히 긴 주기를 가진 PRNG를 사용하는 것이 중요합니다.

암호학적 의사 난수 생성기

보안이 중요한 상황에서는 암호학적으로 안전한 의사 난수 생성기(CSPRNG)를 사용해야 합니다.

이는 일반 PRNG보다 예측이 훨씬 어렵습니다. Rust의 rand 크레이트에는 CSPRNG도 포함되어 있습니다.

rand 크레이트 사용하기

Rust에서 난수를 생성하려면 rand 크레이트를 사용합니다.

이 크레이트는 다양한 난수 생성기를 제공하며, 사용하기도 매우 쉽습니다.

Cargo.toml 설정

먼저, 프로젝트의 Cargo.toml 파일에 rand 크레이트를 추가합니다.

[dependencies]
rand = "0.8"

기본 사용법

이제 rand 크레이트를 사용하여 난수를 생성하는 방법을 알아보겠습니다.

다음은 간단한 예제입니다.

use rand::Rng;

fn main() {
    // 기본 난수 생성기 초기화
    let mut rng = rand::thread_rng();

    // i32 타입의 난수 생성
    let i: i32 = rng.gen();

    // 생성된 난수 출력
    println!("Generated random number: {}", i);
}

여기서 rand::thread_rng()는 현재 스레드에서 사용할 수 있는 가장 빠르고 안전한 난수 생성기를 반환합니다.

rng.gen() 메서드는 랜덤한 값을 생성합니다.

다양한 타입의 난수 생성

rand 크레이트는 다양한 타입의 난수를 생성할 수 있습니다.

예를 들어, 정수, 부동소수점, boolean 등을 생성할 수 있습니다.

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();

    // 정수형 난수 생성
    let int: i32 = rng.gen();
    println!("Generated integer: {}", int);

    // 부동 소수점 난수 생성
    let float: f64 = rng.gen();
    println!("Generated float: {}", float);

    // Boolean 난수 생성
    let boolean: bool = rng.gen();
    println!("Generated boolean: {}", boolean);
}

범위 지정 난수 생성

특정 범위 내에서 난수를 생성하고 싶다면, gen_range 메서드를 사용하면 됩니다.

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();

    // 1부터 10까지의 정수형 난수 생성
    let int_in_range = rng.gen_range(1..=10);
    println!("Generated integer in range: {}", int_in_range);

    // 1.0부터 10.0까지의 부동 소수점 난수 생성
    let float_in_range = rng.gen_range(1.0..10.0);
    println!("Generated float in range: {}", float_in_range);
}

특정 분포에 따른 난수 생성

rand 크레이트는 특정 분포를 따르는 난수도 생성할 수 있습니다.

예를 들어, 베르누이 분포를 따르는 난수를 생성해 보겠습니다.

use rand::distributions::{Bernoulli, Distribution};

fn main() {
    let mut rng = rand::thread_rng();

    // 0.3 확률로 true를 반환하는 베르누이 분포 생성
    let bernoulli = Bernoulli::new(0.3).unwrap();

    // 베르누이 분포에 따른 난수 생성
    let sample = bernoulli.sample(&mut rng);
    println!("Generated Bernoulli random value: {}", sample);
}

rand_core 크레이트

rand_core 크레이트는 난수 생성기를 직접 구현할 때 사용됩니다.

예를 들어, SIMD를 사용한 메르센 트위스터를 구현해 보겠습니다.

use rand_core::{RngCore, SeedableRng};
use std::arch::x86_64::_mm_setzero_si128;

#[derive(Clone)]
pub struct MyRng {
    state: [__m128i; 4],
    index: usize,
}

impl SeedableRng for MyRng {
    type Seed = [u8; 16];

    fn from_seed(seed: [u8; 16]) -> Self {
        let state = unsafe { [_mm_setzero_si128(); 4] };
        MyRng { state, index: 0 }
    }
}

impl RngCore for MyRng {
    fn next_u32(&mut self) -> u32 {
        // 난수 생성 로직 구현
        42 // 임시로 42 반환
    }

    fn next_u64(&mut self) -> u64 {
        84 // 임시로 84 반환
    }

    fn fill_bytes(&mut self, dest: &mut [u8]) {
        for byte in dest.iter_mut() {
            *byte = self.next_u32() as u8;
        }
    }

    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
        self.fill_bytes(dest);
        Ok(())
    }
}

이렇게 직접 난수 생성기를 구현할 수도 있지만, 대부분의 경우 rand 크레이트에서 제공하는 기능을 사용하는 것이 더 간편합니다.

결론

이제 Rust에서 난수를 생성하는 방법을 이해하셨나요?

rand 크레이트를 사용하면 다양한 타입과 분포의 난수를 쉽게 생성할 수 있습니다.

프로젝트에 맞는 난수 생성 방법을 선택하여 활용해 보세요!

모두 즐겁고 계획적인 개발 되시길 바랍니다!