Idiomatic type for a pair

Using the rand crate, I want to implement the following:

/// Univariate standard normal distribtion with pairwise output
#[derive(Clone, Copy, Default, Debug)]
pub struct StdNormDistPair;

impl<T: Num> Distribution<(T, T)> for StdNormDistPair {
    /* … */
}

I wondered if it is semantically correct to use the type (T, T) to describe a pair or whether it would be better to make it [T; 2]. I would like to know what's the idiomatic way to describe a "pair of two equally-typed values".

Interestingly, std uses a tuple rather than an array in its sin_cos method. This would be an argument in favor of using tuples.

However, arrays would allow me to iterate, for example.

There is no single idiomatic representation for that.
I'd just let it depend on the use case, whatever is easier to consume is what will work best.

If it's an open-ended library meant for general consumption, may I suggest ¿porqué no los dos?

2 Likes

Following the precedent of multi-dimentional distributions in rand_distr - Rust, such as this one it looks like [F; 2] is the way to go, at least in case consistency with that library is a goal ^^

1 Like

I feel like [T; 2] (the array) fits my purpose best. Basically I create a list (that happens to have two elements).

I see a semantical difference to the sin_cos method, where each element of the pair has a special role/meaning.


P.S.: In some cases, type inference seems to have more issues with [T; 2], but still trying to figure that out.


Playground doesn't seem to work right (and I don't have a minimal example yet), but basically what happens is this:

impl<T: Num> Distribution<Vec<T>> for StdNormDistVec {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec<T> {
        let dim = self.0;
        let mut vec = Vec::with_capacity(dim);
        for _ in 0..dim / 2 {
            let (x, y) = StdNormDistPair.sample(rng);
            vec.push(x);
            vec.push(y);
        }
        if dim % 2 == 1 {
            vec.push(StdNormDist.sample(rng));
        }
        vec
    }
}

If I implement both, as @jjpe suggested, and if I replace (x, y) with [x, y], then the compiler says I need type annotations.

Here is a minimal example, I believe:

trait PairGen<P> {
    fn get_pair(&self) -> P;
}
struct Foo;
impl<T: Default> PairGen<(T, T)> for Foo {
    fn get_pair(&self) -> (T, T) {
        Default::default()
    }
}
impl<T: Default> PairGen<[T; 2]> for Foo {
    fn get_pair(&self) -> [T; 2] {
        Default::default()
    }
}
fn main() {
    let mut vec = Vec::<f64>::new();
    let (x, y) = Foo.get_pair(); // this works
    vec.push(x);
    vec.push(y);
    let [x, y] = Foo.get_pair(); // this fails
    vec.push(x);
    vec.push(y);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
  --> src/main.rs:20:9
   |
20 |     let [x, y] = Foo.get_pair(); // this fails
   |         ^^^^^^
   |
help: consider giving this pattern a type
   |
20 |     let [x, y]: /* Type */ = Foo.get_pair(); // this fails
   |               ++++++++++++

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground` (bin "playground") due to previous error


Interestingly, the following simpler example works fine:

fn bar<T: Default>() -> [T; 2] {
    Default::default()
}

fn main() {
    let mut vec = Vec::<f64>::new();
    let [x, y] = bar(); // this works
    vec.push(x);
    vec.push(y);
}

(Playground)


Is this something that could/should be improved in the compiler?

Curiously enough, if we drop the implementation of PairGen<(T, T)> and use only PairGen<[T; 2]> - no error, playground with proof.

2 Likes

Removing the tuple impl works because when there's only one implementation, that implementation will be used to guide inference. You can replace the tuple impl with one for f64 or String for that matter and the error will come back.

4 Likes

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.