Theodor Johnson
Theodor Johnson

Reputation: 257

How can I accept an arbitrary container that is sliceable by `..`?

I have the following simple setup:

pub trait Distribution {
    type T;
    fn sample(&self) -> Self::T;
}

pub fn foo<D, U>(dist: &D, f: &Fn(&[f64]))
where
    D: Distribution<T = U>,
    U: std::ops::Index<usize>,
{
    let s = dist.sample();
    f(&s[..]);
}

foo accepts a generic container that implements Distribution and a function, but if I use it in an example like this:

struct Normal {}

impl Distribution for Normal {
    type T = Vec<f64>;
    fn sample(&self) -> Self::T {
        vec![0.0]
    }
}

fn main() {
    let x = Normal {};
    let f = |_x: &[f64]| {};
    foo(&x, &f);
}

It does not work, because f(&s[..]); is not of type slice:

error[E0308]: mismatched types
  --> src/main.rs:12:10
   |
12 |     f(&s[..]);
   |          ^^ expected usize, found struct `std::ops::RangeFull`
   |
   = note: expected type `usize`
              found type `std::ops::RangeFull`

error[E0308]: mismatched types
  --> src/main.rs:12:7
   |
12 |     f(&s[..]);
   |       ^^^^^^ expected slice, found associated type
   |
   = note: expected type `&[f64]`
              found type `&<U as std::ops::Index<usize>>::Output`

Upvotes: 2

Views: 497

Answers (1)

Shepmaster
Shepmaster

Reputation: 431649

You say that you are going to index your slice with a usize:

U: std::ops::Index<usize>,

Then you index the slice by something that is not a usize:

f(&s[..]);

This is a RangeFull. Rightly, the compiler doesn't allow you to lie to it about types.

Instead, if you use the correct types, as shown in the error message, it works:

U: std::ops::Index<std::ops::RangeFull>,

Then the error is about the output type of the indexing. See Requiring implementation of Mul in generic function for a full description.

U: std::ops::Index<std::ops::RangeFull, Output = [f64]>,

That being said...

  1. There's no reason to introduce the generic type U. Instead, add trait bounds on the associated type of D.
  2. It's unlikely that you wish to force dynamic dispatch of your closure. Instead, take it as a generic type:
use std::ops::{Index, RangeFull};

pub fn foo<D, F>(dist: &D, f: F)
where
    D: Distribution,
    D::T: Index<RangeFull, Output = [f64]>,
    F: Fn(&[f64]),
{
    let s = dist.sample();
    f(&s[..]);
}

Or equivalently:

pub fn foo<D>(dist: &D, f: impl Fn(&[f64]))
where
    D: Distribution,
    D::T: std::ops::Index<std::ops::RangeFull, Output = [f64]>,

However, using RangeFull for this level of generic doesn't feel right to me. I'd use AsRef instead:

pub fn foo<D>(dist: &D, f: impl Fn(&[f64]))
where
    D: Distribution,
    D::T: AsRef<[f64]>,
{
    let s = dist.sample();
    f(s.as_ref());
}

Upvotes: 8

Related Questions