Skip to content

Commit 6cb75ee

Browse files
authored
Make UniformUsize serializable (#1646)
- [x] Added a `CHANGELOG.md` entry # Motivation This allows `WeightedIndex<usize>` to be serializable.
1 parent 0c955c5 commit 6cb75ee

File tree

11 files changed

+143
-22
lines changed

11 files changed

+143
-22
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
1212
### Deprecated
1313
- Deprecate `rand::rngs::mock` module and `StepRng` generator (#1634)
1414

15+
### Additions
16+
- Enable `WeightedIndex<usize>` (de)serialization (#1646)
17+
1518
## [0.9.1] - 2025-04-17
1619
### Security and unsafe
1720
- Revise "not a crypto library" policy again (#1565)

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,4 @@ rand_pcg = { path = "rand_pcg", version = "0.9.0" }
8181
# Only to test serde
8282
bincode = "1.2.1"
8383
rayon = "1.7"
84+
serde_json = "1.0.140"

benches/benches/generators.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ pub fn reseeding_bytes(c: &mut Criterion) {
196196
g.throughput(criterion::Throughput::Bytes(1024 * 1024));
197197

198198
fn bench(g: &mut BenchmarkGroup<WallTime>, thresh: u64) {
199-
let name = format!("chacha20_{}k", thresh);
199+
let name = format!("chacha20_{thresh}k");
200200
g.bench_function(name.as_str(), |b| {
201201
let mut rng = ReseedingRng::<ChaCha20Core, _>::new(thresh * 1024, OsRng).unwrap();
202202
let mut buf = [0u8; 1024 * 1024];

benches/benches/seq_choose.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn bench(c: &mut Criterion) {
3030

3131
let lens = [(1, 1000), (950, 1000), (10, 100), (90, 100)];
3232
for (amount, len) in lens {
33-
let name = format!("seq_slice_choose_multiple_{}_of_{}", amount, len);
33+
let name = format!("seq_slice_choose_multiple_{amount}_of_{len}");
3434
c.bench_function(name.as_str(), |b| {
3535
let mut rng = Pcg32::from_rng(&mut rand::rng());
3636
let mut buf = [0i32; 1000];
@@ -54,7 +54,7 @@ pub fn bench(c: &mut Criterion) {
5454

5555
let lens = [(1, 1000), (950, 1000), (10, 100), (90, 100)];
5656
for (amount, len) in lens {
57-
let name = format!("seq_slice_choose_multiple_weighted_{}_of_{}", amount, len);
57+
let name = format!("seq_slice_choose_multiple_weighted_{amount}_of_{len}");
5858
c.bench_function(name.as_str(), |b| {
5959
let mut rng = Pcg32::from_rng(&mut rand::rng());
6060
let mut buf = [0i32; 1000];

benches/benches/weighted.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn bench(c: &mut Criterion) {
4949
(1000, 1_000_000, "1M"),
5050
];
5151
for (amount, length, len_name) in lens {
52-
let name = format!("weighted_sample_indices_{}_of_{}", amount, len_name);
52+
let name = format!("weighted_sample_indices_{amount}_of_{len_name}");
5353
c.bench_function(name.as_str(), |b| {
5454
let length = black_box(length);
5555
let amount = black_box(amount);

rand_core/src/block.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ mod test {
557557
}
558558
rng.next_u32();
559559

560-
let result = rng.next_u64();
560+
let _ = rng.next_u64();
561561
assert_eq!(rng.index(), 1);
562562
}
563563
}

rand_core/src/impls.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -179,39 +179,39 @@ mod test {
179179
fn test_fill_via_u32_chunks() {
180180
let src_orig = [1u32, 2, 3];
181181

182-
let mut src = src_orig;
182+
let src = src_orig;
183183
let mut dst = [0u8; 11];
184-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (3, 11));
184+
assert_eq!(fill_via_chunks(&src, &mut dst), (3, 11));
185185
assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0]);
186186

187-
let mut src = src_orig;
187+
let src = src_orig;
188188
let mut dst = [0u8; 13];
189-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (3, 12));
189+
assert_eq!(fill_via_chunks(&src, &mut dst), (3, 12));
190190
assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0]);
191191

192-
let mut src = src_orig;
192+
let src = src_orig;
193193
let mut dst = [0u8; 5];
194-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (2, 5));
194+
assert_eq!(fill_via_chunks(&src, &mut dst), (2, 5));
195195
assert_eq!(dst, [1, 0, 0, 0, 2]);
196196
}
197197

198198
#[test]
199199
fn test_fill_via_u64_chunks() {
200200
let src_orig = [1u64, 2];
201201

202-
let mut src = src_orig;
202+
let src = src_orig;
203203
let mut dst = [0u8; 11];
204-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (2, 11));
204+
assert_eq!(fill_via_chunks(&src, &mut dst), (2, 11));
205205
assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]);
206206

207-
let mut src = src_orig;
207+
let src = src_orig;
208208
let mut dst = [0u8; 17];
209-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (2, 16));
209+
assert_eq!(fill_via_chunks(&src, &mut dst), (2, 16));
210210
assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]);
211211

212-
let mut src = src_orig;
212+
let src = src_orig;
213213
let mut dst = [0u8; 5];
214-
assert_eq!(fill_via_chunks(&mut src, &mut dst), (1, 5));
214+
assert_eq!(fill_via_chunks(&src, &mut dst), (1, 5));
215215
assert_eq!(dst, [1, 0, 0, 0, 0]);
216216
}
217217
}

rand_core/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -764,9 +764,11 @@ mod test {
764764
let mut rng = rng.unwrap_mut();
765765

766766
assert_eq!(rng.next_u32(), 4);
767-
let mut rng2 = rng.re();
768-
assert_eq!(rng2.next_u32(), 4);
769-
drop(rng2);
767+
{
768+
let mut rng2 = rng.re();
769+
assert_eq!(rng2.next_u32(), 4);
770+
// Make sure rng2 is dropped.
771+
}
770772
assert_eq!(rng.next_u32(), 4);
771773
}
772774
}

src/distr/uniform_int.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,28 @@ uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) }
402402
/// this implementation will use 32-bit sampling when possible.
403403
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
404404
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405+
#[cfg_attr(all(feature = "serde"), derive(Serialize))]
406+
// To be able to deserialize on 32-bit we need to replace this with a custom
407+
// implementation of the Deserialize trait, to be able to:
408+
// - panic when `mode64` is `true` on 32-bit,
409+
// - assign the default value to `mode64` when it's missing on 64-bit,
410+
// - panic when the `usize` fields are greater than `u32::MAX` on 32-bit.
411+
#[cfg_attr(
412+
all(feature = "serde", target_pointer_width = "64"),
413+
derive(Deserialize)
414+
)]
405415
pub struct UniformUsize {
416+
/// The lowest possible value.
406417
low: usize,
418+
/// The number of possible values. `0` has a special meaning: all.
407419
range: usize,
420+
/// Threshold used when sampling to obtain a uniform distribution.
408421
thresh: usize,
422+
/// Whether the largest possible value is greater than `u32::MAX`.
409423
#[cfg(target_pointer_width = "64")]
424+
// Handle missing field when deserializing on 64-bit an object serialized
425+
// on 32-bit. Can be removed when switching to a custom deserializer.
426+
#[cfg_attr(feature = "serde", serde(default))]
410427
mode64: bool,
411428
}
412429

@@ -793,4 +810,94 @@ mod tests {
793810
);
794811
}
795812
}
813+
814+
#[test]
815+
fn test_uniform_usize_empty_range() {
816+
assert_eq!(UniformUsize::new(10, 10), Err(Error::EmptyRange));
817+
assert!(UniformUsize::new(10, 11).is_ok());
818+
819+
assert_eq!(UniformUsize::new_inclusive(10, 9), Err(Error::EmptyRange));
820+
assert!(UniformUsize::new_inclusive(10, 10).is_ok());
821+
}
822+
823+
#[test]
824+
fn test_uniform_usize_constructors() {
825+
assert_eq!(
826+
UniformUsize::new_inclusive(u32::MAX as usize, u32::MAX as usize),
827+
Ok(UniformUsize {
828+
low: u32::MAX as usize,
829+
range: 1,
830+
thresh: 0,
831+
#[cfg(target_pointer_width = "64")]
832+
mode64: false
833+
})
834+
);
835+
assert_eq!(
836+
UniformUsize::new_inclusive(0, u32::MAX as usize),
837+
Ok(UniformUsize {
838+
low: 0,
839+
range: 0,
840+
thresh: 0,
841+
#[cfg(target_pointer_width = "64")]
842+
mode64: false
843+
})
844+
);
845+
#[cfg(target_pointer_width = "64")]
846+
assert_eq!(
847+
UniformUsize::new_inclusive(0, u32::MAX as usize + 1),
848+
Ok(UniformUsize {
849+
low: 0,
850+
range: u32::MAX as usize + 2,
851+
thresh: 1,
852+
mode64: true
853+
})
854+
);
855+
#[cfg(target_pointer_width = "64")]
856+
assert_eq!(
857+
UniformUsize::new_inclusive(u32::MAX as usize, u64::MAX as usize),
858+
Ok(UniformUsize {
859+
low: u32::MAX as usize,
860+
range: u64::MAX as usize - u32::MAX as usize + 1,
861+
thresh: u32::MAX as usize,
862+
mode64: true
863+
})
864+
);
865+
}
866+
867+
// This could be run also on 32-bit when deserialization is implemented.
868+
#[cfg(all(feature = "serde", target_pointer_width = "64"))]
869+
#[test]
870+
fn test_uniform_usize_deserialization() {
871+
use serde_json;
872+
let original = UniformUsize::new_inclusive(10, 100).expect("creation");
873+
let serialized = serde_json::to_string(&original).expect("serialization");
874+
let deserialized: UniformUsize =
875+
serde_json::from_str(&serialized).expect("deserialization");
876+
assert_eq!(deserialized, original);
877+
}
878+
879+
#[cfg(all(feature = "serde", target_pointer_width = "64"))]
880+
#[test]
881+
fn test_uniform_usize_deserialization_from_32bit() {
882+
use serde_json;
883+
let serialized_on_32bit = r#"{"low":10,"range":91,"thresh":74}"#;
884+
let deserialized: UniformUsize =
885+
serde_json::from_str(&serialized_on_32bit).expect("deserialization");
886+
assert_eq!(
887+
deserialized,
888+
UniformUsize::new_inclusive(10, 100).expect("creation")
889+
);
890+
}
891+
892+
#[cfg(all(feature = "serde", target_pointer_width = "64"))]
893+
#[test]
894+
fn test_uniform_usize_deserialization_64bit() {
895+
use serde_json;
896+
let original = UniformUsize::new_inclusive(1, u64::MAX as usize - 1).expect("creation");
897+
assert!(original.mode64);
898+
let serialized = serde_json::to_string(&original).expect("serialization");
899+
let deserialized: UniformUsize =
900+
serde_json::from_str(&serialized).expect("deserialization");
901+
assert_eq!(deserialized, original);
902+
}
796903
}

src/seq/iterator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ pub trait IteratorRandom: Iterator + Sized {
137137
//
138138
// Clippy is wrong here: we need to iterate over all entries with the RNG to
139139
// ensure that choosing is *stable*.
140+
// "allow(unknown_lints)" can be removed when switching to at least
141+
// rust-version 1.86.0, see:
142+
// https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last
143+
#[allow(unknown_lints)]
140144
#[allow(clippy::double_ended_iterator_last)]
141145
fn choose_stable<R>(mut self, rng: &mut R) -> Option<Self::Item>
142146
where

0 commit comments

Comments
 (0)