How to limit a generic in the function for an implementation

I could use some help figuring out the best way in rust to implement a function that requires different implementations for different generic types of numbers. See this code, which compiles and runs fine:

use rand::Rng;
use rand::prelude::Distribution;
use rand::distributions::Standard;
use geo::{ Point, CoordNum};

trait RandomPoint
{
	type CoordType;

	fn random() -> Self;

	fn random_ceil(max: Self::CoordType) -> Self;
}

impl<T: num_traits::Signed> RandomPoint for Point<T>
	where T: CoordNum + rand::distributions::uniform::SampleUniform, Standard: Distribution<T>,
{
	type CoordType = T;

	fn random() -> Point<T> {
		let mut rng = rand::thread_rng();
		Point::new(rng.gen(), rng.gen())
	}

	fn random_ceil(max: T) -> Point<T>
//		where T: num_traits::Signed, // cannot have both
	{
		let mut rng = rand::thread_rng();
		let min = -max; // cannot apply unary operator `-` unless limited to <T: num_traits:Signed> as above
		Point::new(rng.gen_range(min..=max),
				   rng.gen_range(min..=max))
	}
}


fn main() {
	let p1 = Point::new(1., 2.);
	let p2 = Point::new(1, 2);
	let p_random: Point<f32> = Point::random();
	let p_random2: Point<i32> = Point::random();
	let p_rand_ceil: Point<i32> = Point::random_ceil(10);

	dbg!(p1);
    dbg!(p2);
    dbg!(p_random);
    dbg!(p_random2);
    dbg!(p_rand_ceil);
}

Right now, the above implementation of RandomPoint is limited to Signed coordinates. I would like to know the best way to implement the random_ceil function so that it can handle both Signed and Unsigned.

Do I need to write a whole second implementation, like:

impl<T: num_traits::Unsigned> RandomPoint for Point<T>
...

It doesn't seem like that is possible, as I get "conflicting implementations of trait RandomPoint for type `geo::Point<_>" from the compiler if I try to implement it twice.

So is there a better way to accomplish this? Could I limit the random_ceil function instead, so that I can have two different random_ceil functions in the same implementation, one for Signed and one for Unsigned coordinates (presumably for unsigned, min would be set to 0 somehow)? Any suggestions would be much appreciated!

For this particular case, you could use Zero and SaturatingSub.

// (Some code elided)
impl<T> RandomPoint for Point<T>
where
    T: num_traits::SaturatingSub + num_traits::Zero,
{
	fn random_ceil(max: T) -> Point<T>	{
		let mut rng = rand::thread_rng();
		let min = T::zero().saturating_sub(&max);
		Point::new(
            rng.gen_range(min..=max),
            rng.gen_range(min..=max),
        )
    }
}

Intriguing, but SaturatingSub appears to be only implemented for integers, not for floats. So I am still stuck with having random_ceil either handle signed number or handling integers, but not able to handle all numbers. Short of adding a second function, something like, random_ceil_min0, is there a better way to handle all numbers in random_ceil?

Upon further investigation, enabling core_intrinsics and using cargo +nightly run will work. Please feel free to suggest a better solution that does not require this unstable feature! Example code:

#![feature(core_intrinsics)]

use rand::Rng;
use rand::prelude::Distribution;
use rand::distributions::Standard;
use geo::{ Point, CoordNum};

trait RandomPoint
{
	type CoordType;

	fn random() -> Self;

	fn random_ceil(max: Self::CoordType) -> Self;
}

impl<T> RandomPoint for Point<T>
	where T:  CoordNum + rand::distributions::uniform::SampleUniform, Standard: Distribution<T>,
{
	type CoordType = T;

	fn random() -> Point<T> {
		let mut rng = rand::thread_rng();
		Point::new(rng.gen(), rng.gen())
	}

	fn random_ceil(max: T) -> Point<T>
	{

		let mut rng = rand::thread_rng();
		let min = std::intrinsics::saturating_sub(T::zero(), max); // -max or 0
		Point::new(rng.gen_range(min..=max),
				   rng.gen_range(min..=max))
	}
}

fn main() {
	let p1 = Point::new(1., 2.);
	let p2 = Point::new(1, 2);
	let p_random: Point<f32> = Point::random();
	let p_random2: Point<i32> = Point::random();
	let p_rand_ceil: Point<i32> = Point::random_ceil(10);
	let p_rand_uint: Point<u8> = Point::random_ceil(10);

	dbg!(p1);
        dbg!(p2);
        dbg!(p_random);
        dbg!(p_random2);
        dbg!(p_rand_ceil);
        dbg!(p_rand_uint);
}

Tried it and still doesn't work.. When I actually try to pass a float to random_ceil, I get:

error[E0511] : invalid monomorphization of saturating_sub intrinsic: expected basic integer type, found f32

So even though I thought saturating sub took all numbers, it apparently balks at floats. So I still don't know how to handle both floats and non-floats at the same time.

Ah, sorry about that. I guess you need a different motley collection of ops. Does this cover all the types you need?

// Assumes `max >= 0`, perhaps return `max` itself is < 0
// (e.g. `-128i8` will overflow)
fn min_from<T>(max: T) -> T
where
    T: Num + Bounded + PartialOrd,
{
	let zero = T::zero();
	if T::min_value() < zero {
	    // Signed
	    zero - max
    } else {
        // Unsigned
        zero
    }
}

As for the original question, Rust doesn't have mutually exclusive traits yet, so it can't understand that an implementation bounded on Signed cannot overlap with one bounded on Unsigned. Thus two generic implementations will overlap -- what if something implements both Signed and Unsigned? (There's no way to express that it is impossible yet.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.