# 더 작고 일관된 난수 API를 위해 rand를 포크한 이유

> Clean Markdown view of GeekNews topic #32027. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=32027](https://news.hada.io/topic?id=32027)
- GeekNews Markdown: [https://news.hada.io/topic/32027.md](https://news.hada.io/topic/32027.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-08-01T09:07:13+09:00
- Updated: 2026-08-01T09:07:13+09:00
- Original source: [casualhacks.net](https://casualhacks.net/blog/2026-07-27-why-i-forked-rand.html)
- Points: 1
- Comments: 1

## Topic Body

- Rust의 대표 난수 크레이트 `rand`는 일상적인 연산이 여러 특성에 흩어져 있어, 더 작은 공개·구현 표면과 일관된 사용 경험을 갖춘 **urandom**을 개발함
- 고수준 연산을 하나의 `Random` 구조체에 모으고 `Rng` 특성을 봉인해, 임의 생성기 지원보다 **API 발견성**과 내부 최적화를 우선함
- 새로운 난수 알고리듬을 도입하지 않고 Xoshiro256의 출력 함수를 용도별로 선택해, 1,000개 `f64` 생성 벤치마크에서 `rand` 0.10.2보다 약 **31% 높은 처리량**을 기록함
- 균등 정수 표본 추출은 임계값을 지연 계산하는 하나의 **비편향 구현**으로 재사용·일회성 경로를 통합했으며, `500..20_000` 범위 벤치마크에서 `rand`의 두 경로보다 빨랐음
- 명시적 시드의 원시 출력은 지원 아키텍처와 SemVer 호환 릴리스에서 재현성을 보장하지만, **임의 생성기 연결**과 `rand`의 방대한 분포·서드파티 통합 생태계는 포기함

---

### 하나로 모은 `Random` API
- `rand`의 유용한 연산은 여러 특성에 흩어져 있음
  - 난수 범위 생성에는 `RngExt`, 시퀀스 선택에는 `IndexedRandom`, 섞기에는 `SliceRandom`이 필요함
  - `rand` 0.10은 일회성 호출을 위한 `rand::random_range` 같은 루트 수준 도우미를 제공함
  - RNG 핸들을 유지하거나 선택·섞기 같은 시퀀스 연산을 사용하려면 여전히 여러 특성의 메서드를 찾아야 함
- prelude로 가져오기를 줄여도 확장 메서드가 RNG, 슬라이스, 반복자 중 어느 타입에 적용되는지 알아야 하므로 IDE 자동 완성만으로 찾기 어려움
- [urandom](https://crates.io/crates/urandom)은 고수준 소비자 API를 하나의 **`Random` 래퍼 구조체**에 배치함
  - `urandom::new()`로 `Random&lt;urandom::rng::Xoshiro256Rng&gt;`를 생성함
  - `uniform`, `choose`, `shuffle`을 같은 객체에서 호출할 수 있음
  - 자동 완성으로 `random`, `uniform`, `chance`, `choose`, `shuffle`, `sample` 등을 확인할 수 있음
  - 모두 고유 메서드이므로 고수준 확장 특성을 찾거나 가져올 필요가 없음

### 확장성 대신 최적화를 택한 봉인된 `Rng`
- `rand`는 저수준 RNG 특성을 공개 확장 지점으로 취급하지만, `urandom`의 **`Rng` 특성은 봉인**되어 지원 생성기를 크레이트 내부에서 선택하고 구현함
  - 임의 생성기를 `Random`에 연결할 수 없음
  - 새 생성기를 추가하려면 `urandom` 자체를 변경해야 함
- 더 나은 알고리듬이 목적이라면 현재 역할별 기본 선택으로 Xoshiro256과 ChaCha가 이미 자리 잡고 있으며, 권장 사항도 천천히 바뀜
  - 더 나은 선택지가 생기면 향후 메이저 릴리스에서 채택할 수 있음
- 다른 프로젝트·프로그래밍 언어·레거시 알고리듬·특수 하드웨어·시뮬레이션 전용 생성기와 호환하려면 생성기만 같아서는 부족함
  - 균등 표본 추출과 섞기 등 관련 알고리듬까지 같아야 하므로 전체 계약을 구현한 전용 구현이 더 적합함
- 봉인된 특성 덕분에 알 수 없는 생성기와 예외 상황을 위한 구현 계약을 설계·문서화하지 않고 `urandom`에 필요한 원시 연산만 추가할 수 있음
  - 생성기와 알고리듬을 서로 맞춰 특수화할 수 있어 `rand`에서는 사용할 수 없는 일부 최적화가 가능함
- 대부분의 애플리케이션에서는 새 PRNG 구현보다 **엔트로피 선택**이 더 유용함
  - 구체적인 생성기는 네이티브 `from_seed` 생성자를 공개함
  - `ChaCha12Rng::from_seed(seed)`처럼 명시적 시드를 사용한 `Random`을 만들 수 있음
  - 임의 RNG 구현은 받지 않지만 고급 사용자가 필요로 할 것으로 예상되는 확장 지점은 유지함

### 같은 알고리듬에서 얻은 성능 향상
- `urandom`은 새로운 난수 생성 알고리듬을 사용하지 않음
  - 64비트 시스템에서 비암호학적 용도의 `urandom::new()`와 `rand::rngs::SmallRng`은 같은 **Xoshiro256 계열**을 사용함
  - 암호학적 용도의 `urandom::csprng()`와 `rand::rngs::StdRng`은 **ChaCha12**를 사용함
  - 편의 함수 `rand::rng()`의 내부 생성기도 ChaCha12임
- `rand`의 생성기 인터페이스는 정수 워드와 바이트 채우기를 제공하므로 `f64`가 필요한 분포도 전체 `u64`부터 요청함
- `urandom::Rng`은 `next_u32`, `next_u64`뿐 아니라 **`next_f32`와 `next_f64`** 도 제공함
  - 부동소수점 난수는 전체 워드보다 적은 난수 비트가 필요함
  - 생성기는 이 메서드들을 더 저렴한 출력 함수로 재정의할 수 있음
- Xoshiro 구현은 상태 전이를 공유하면서 출력 경로를 구분함
  - `u64`에는 Xoshiro256++를 유지함
  - `u32`와 부동소수점에는 상위 비트가 해당 용도에 맞게 설계된 더 빠른 Xoshiro256+를 사용함
- `urandom` 1.0과 `rand` 0.10.2로 각각 1,000개의 난수를 생성한 마이크로벤치마크 결과는 다음과 같음
  - Xoshiro `u64`: 양쪽 모두 **814ns**
  - Xoshiro `u32`: `rand` 836ns, `urandom` 788ns
  - Xoshiro `f64`: `rand` 1,033ns, `urandom` 788ns
  - ChaCha12 `f64`: `rand` 2,199ns, `urandom` 2,011ns
- Xoshiro `f64`의 종단 간 처리량은 약 31% 높고 실행 시간은 24% 짧았지만, 동일한 작업을 수행하는 `u64` 경로는 사실상 동률이었음
- ChaCha12는 `next_f64`를 재정의하지 않아 성능이 대체로 비슷함
- 정확한 시간은 머신과 컴파일러에 따라 달라지며, 자세한 조건은 [전체 벤치마크 노트](https://github.com/CasualX/urandom/blob/master/docs/benchmarks-rand.md)에서 확인할 수 있음

### 하나로 통합한 균등 표본 추출 경로
- 정수를 범위 길이로 단순 나머지 연산하면 **편향**이 생기므로, 올바른 균등 정수 표본 추출은 생성기 출력 일부를 거부해야 함
- 정확한 거부 임계값 계산에는 비용이 큰 나머지 연산이 필요함
  - 표본 추출기를 반복 사용한다면 초기 설정 비용으로 감당할 수 있음
  - 값 하나만 생성할 때는 이 비용이 상대적으로 커짐
- `rand`는 이러한 차이를 `UniformSampler` 특성으로 노출함
  - 생성된 `UniformInt`는 임계값을 미리 계산해 편향 없이 표본을 추출함
  - `Rng::random_range`는 초기 설정을 피하려고 별도의 `sample_single` 또는 `sample_single_inclusive` 훅을 사용함
  - 기본 기능에서는 일회성 단축 경로가 조금 편향된 두 번째 알고리듬을 사용함
  - 선택적 `unbiased` 기능은 이를 더 복잡한 반복 버전으로 교체함
- `urandom`은 임계값을 **지연 계산**해 재사용 및 일회성 범위 모두에 하나의 비편향 곱셈·거부 구현을 사용함
  - Daniel Lemire의 2018년 논문 [Fast Random Integer Generation in an Interval](https://arxiv.org/abs/1805.10941)에 기술된 방식을 따름
  - 대부분의 실용적 범위에서는 첫 후보가 나눗셈 전에 반환됨
  - 첫 후보를 반환할 수 없으면 정확한 임계값을 계산한 뒤 편향 없이 반복함
  - 전체 범위가 요청되는 `range == 0` 예외도 처리함
- 별도 메서드나 두 번째 알고리듬, 선행 설정 비용, 편향된 고속 경로 없이 동일한 구현으로 재사용 분포와 일회성 범위를 처리함
- `500..20_000` 범위에서 1,000개를 추출한 벤치마크 결과는 다음과 같음
  - 재사용 `UniformInt`: `rand` 1,098ns, `urandom` **950ns**
  - 일회성 범위: `rand` 1,079ns, `urandom` **942ns**
- `rand` 결과는 기본 기능 기준이므로 더 빠른 일회성 행도 약간 편향된 경로인 반면, `urandom`은 비편향 상태로 두 경로보다 빨랐음

### 릴리스와 아키텍처를 아우르는 재현성
- `urandom`은 **재현성**을 공개 계약의 일부로 취급함
  - 같은 명시적 시드와 같은 저수준 RNG 호출 순서가 주어지면 결정론적 생성기의 원시 출력이 유지됨
  - 지원 아키텍처와 SemVer 호환 릴리스 전체에서 안정성을 보장함
  - 64비트 서버와 32비트 WebAssembly 클라이언트가 재생을 위한 동일한 생성기 기반을 사용할 수 있음
- 이 호환성을 유지하기 위해 32비트 아키텍처에서는 성능을 희생함
- [`rand`의 재현성 정책](https://rust-random.github.io/book/crate-reprod.html)보다 강한 보장임
  - `rand`의 이식 가능한 생성기와 표본 추출 알고리듬은 마이너 릴리스에서 출력이 달라질 수 있음
  - `SmallRng`과 `StdRng`은 명시적으로 이식 가능하지 않으며 플랫폼이나 라이브러리 릴리스에 따라서도 바뀔 수 있음

### 선택의 대가와 적용 기준
- `urandom`은 일반 연산을 `Random`에 모아 확장 특성 없이 쉽게 찾을 수 있게 함
- 생성기와 분포를 함께 설계해 더 저렴한 Xoshiro 출력 경로와 하나의 비편향 균등 표본 추출 경로를 구현함
- 명시적으로 시드가 지정된 생성기의 안정적인 원시 스트림은 **결정론적 게임과 시뮬레이션**에 활용할 수 있음
- 대신 임의 생성기를 가져올 수 없으며, `rand`가 제공하는 더 큰 분포 목록과 서드파티 통합 생태계도 갖추지 못함
- 광범위한 생태계가 필요하면 `rand`가 적합하고, 작은 API 표면·발견성·통합된 최적화·강한 재현성 정책을 선호하면 `urandom`을 선택할 수 있음
- 패키지는 [crates.io](https://crates.io/crates/urandom/1.0.0-alpha.1), [API 문서](https://docs.rs/urandom/1.0.0-alpha.1/urandom/), [GitHub 소스](https://github.com/CasualX/urandom)에서 확인할 수 있음

## Comments



### Comment 62694

- Author: neo
- Created: 2026-08-01T09:08:13+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/kvxee3/why_i_forked_rand) 
- `rand`를 포크할 이유는 충분하지만, **`urandom`이라는 이름**은 `/dev/urandom`과 관련된 라이브러리처럼 들림
  - 유용해 보이지만 이름이 혼동을 부를 수 있음. 글을 읽지 않고 이름만 봤다면 **파일 입출력에 의존한다**고 생각해 살펴보지 않았을 것 같음

- 문제의식에는 동의하지만 `pub fn new() -> Random&lt;impl Rng + Clone&gt;`은 마음에 들지 않음  
  애플리케이션 전체를 `Random&lt;T&gt; where T: Rng`으로 매개변수화하면 번거로운 작업이 늘고, **컴파일 시간**과 `dyn` 관련 문제가 심각해짐. 차라리 `struct Random`이 구체 타입을 갖게 하거나, 차선책으로 `struct Random&lt;T = rng::Xoshiro256Rng&gt;`을 택하겠음

- 비슷한 답답함 때문에 이미 직접 만들어봤지만 포크는 아니며, **`rand`보다 기능이 훨씬 적음**

- 나와 같은 문제를 느낀 누군가가 실제로 해결에 나서서 반가움. Rust에는 이상하게 **트레이트 수프** 라이브러리를 만들게 하는 경향이 있는 듯함  
  업무에서 다루는 데이터베이스의 핵심 자료형은 최소 15개 트레이트를 구현해야 해서 자동완성이 엉망이고 문서도 혼란스러움. 트레이트 수를 일부 줄였지만 순환 의존성이나 핵심 테스트를 작성할 수 없게 되는 문제에 자주 막힘
  - Java 출신의 **아키텍처 우주비행사**들이 같은 객체지향 스타일을 Rust에 적용해서 생기는 현상임. 순환 의존성은 아직 분리할 수 없는 하나의 대상을 억지로 나눴거나, 세 대상을 제대로 구분하지 못했다는 신호임. 코드를 모두 통제할 수 있다면 트레이트 대신 열거형을 쓰면 됨
  - Rust **암호학 생태계**에서는 트레이트 수프 문제가 유독 심해서 미칠 지경임

- 이 라이브러리는 APOSD의 **깊은 인터페이스**와 Filippo의 실수하기 어렵게 설계한 암호학 작업을 모두 떠올리게 하며, 둘 다 큰 찬사임
  - 다만 `urandom::new()`가 **암호학적으로 안전한 난수 생성기**를 반환하지 않으므로 완전히 실수 불가능한 설계는 아님. 특히 Linux의 `/dev/urandom`은 안전하기 때문에 더 헷갈림

- 또 다른 `rand` 대안으로 단순하고 빠른 난수 생성기인 **`fastrand`** 가 있음. `rand`와 `urandom`보다 단순하지만 기능도 더 적음
