Skip to content

Add a way to derive Standard and Uniform distributions for a struct #1524

Closed
@LikeLakers2

Description

@LikeLakers2

Background

What is your motivation?

rand offers the Standard and Uniform distributions, for making random samples of a primitive or struct. However, to implement them for a new type, the impls (impl Distribution<T> for Standard for Standard; impl SampleUniform for T and impl UniformSampler for TUniformSampler for Uniform) must currently be hand-written.

What type of application is this?

No specific type of application, though I write this after having to manually implement Uniform distribution support for the glam crate.

Feature request

I propose that derives be added for the Standard and Uniform distributions. When used on a struct where all fields already implement support for that type of distribution, it will generate the needed code to use Standard/Uniform distributions with that struct.

A derive for the Standard distribution would probably look like this (click the arrow to expand):

Standard distribution derive
#[derive(StandardRand)]
struct Vec3 {
	x: f32,
	y: f32,
	z: f32,
}

This derive would generate code similar to the following:

impl Distribution<Vec3> for Standard {
	fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
		Vec3 {
			x: rng.gen(),
			y: rng.gen(),
			z: rng.gen(),
		}
	}
}

A derive for the Uniform distribution would be much the same, albeit with the addition of a struct being generated for the UniformSampler impl:

Uniform distribution derive
#[derive(UniformRand)]
struct Vec3 {
	x: f32,
	y: f32,
	z: f32,
}

generates the following code:

impl SampleUniform for Vec3 {
	type Sampler = Vec3UniformSampler;
}

struct Vec3UniformSampler {
	x_gen: Uniform<f32>,
	y_gen: Uniform<f32>,
	z_gen: Uniform<f32>,
}

impl UniformSampler for Vec3UniformSampler {
	type X = Vec3;

	fn new<B1, B2>(low_b: B1, high_b: B2) -> Self
	where
		B1: SampleBorrow<Self::X> + Sized,
		B2: SampleBorrow<Self::X> + Sized,
	{
		let low = *low_b.borrow();
		let high = *high_b.borrow();
		// Asserts here are technically optional, since our fields are Uniforms,
		// but I'm including them anyways for the sake of the example.
		assert!(low.x < high.x, "Uniform::new called with `low.x >= high.x");
		assert!(low.y < high.y, "Uniform::new called with `low.y >= high.y");
		assert!(low.z < high.z, "Uniform::new called with `low.z >= high.z");
		Self {
			x_gen: Uniform::new(low.x, high.x),
			y_gen: Uniform::new(low.y, high.y),
			z_gen: Uniform::new(low.z, high.z),
		}
	}

	fn new_inclusive<B1, B2>(low_b: B1, high_b: B2) -> Self
	where
		B1: SampleBorrow<Self::X> + Sized,
		B2: SampleBorrow<Self::X> + Sized,
	{
		let low = *low_b.borrow();
		let high = *high_b.borrow();
		assert!(low.x < high.x, "Uniform::new_inclusive called with `low.x >= high.x");
		assert!(low.y < high.y, "Uniform::new_inclusive called with `low.y >= high.y");
		assert!(low.z < high.z, "Uniform::new_inclusive called with `low.z >= high.z");
		Self {
			x_gen: Uniform::new_inclusive(low.x, high.x),
			y_gen: Uniform::new_inclusive(low.y, high.y),
			z_gen: Uniform::new_inclusive(low.z, high.z),
		}
	}

	fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
		Self::X {
			x: self.x_gen.sample(rng),
			y: self.y_gen.sample(rng),
			z: self.z_gen.sample(rng),
		}
	}
	
	// sample_single() and sample_single_inclusive() are not included for the
	// sake of shortening the example.
}

If I've missed any details, or if there's any questions you have for me regarding this suggestion, please don't hesitate to let me know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions