Trait for sampling

I am trying to create a trait which, if implemented, allows the user to get samples of the implementing struct:

use rand::distributions::{Distribution, Standard};
use rand::Rng;

trait Samplable {
    // TODO: logic necessary for Distribution to implement sample
}

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

struct MyStruct {}

impl Samplable for MyStruct {}

fn main() -> () {
    let mut rng = rand::thread_rng();
    let x: MyStruct = MyStruct::sample(rng);
}

The error that I am getting is:

error[E0277]: the size for values of type `(dyn Samplable + 'static)` cannot be known at compilation time
  --> src/lib.rs:10:6
   |
10 | impl Distribution<dyn Samplable> for Standard {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn Samplable + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

I want to do this because I then want to create other traits that take objects implementing Samplable and do things with them.

Is this project misguided? Is there a better way to do what I am trying to do? If not, how do I (and can I) implement sized for dyn Samplable and what logic needs to go in the Samplable trait?

Return types of methods go in a cpu register or on the stack. In order for that to be implemented, their size needs to be known at compile time.

If you want to return a trait object, you can either return a reference &dyn MyTrait, or a heap allocated trait object, Box<dyn MyTrait>, Rc<dyn MyTrait>, Arc<dyn MyTrait>. Note that traits also need to be object safe for this to work.

The impl for Distribution would rather be:

impl<T: Samplable> Distribution<T> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> dyn Samplable {
        rng.gen()
    }
}

Now T will be sized after you use the trait on a specific type.

Hmmmm... So I implemented

trait Samplable {
    // TODO: logic necessary for Distribution to implement sample
}

impl<T: Samplable> Distribution<T> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> dyn Samplable {
        rng.gen()
    }
}

Now I get:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
   --> src/lib.rs:125:6
    |
125 | impl<T: Samplable> Distribution<T> for Standard {
    |      ^ type parameter `T` must be used as the type parameter for some local type
    |
    = note: implementing a foreign trait is only possible if at least one of the types for which is it implemented is local
    = note: only traits defined in the current crate can be implemented for a type parameter

Sorry I'm still getting the hang of Rust's syntax for generic types. Do I need to somehow add T to the body of the impl statement?

I'm also wondering if I need to implement some logic inside Samplable?

One other question: I have been looking at the Uniform (https://docs.rs/rand/0.6.5/rand/distributions/uniform/index.html) and Standard (https://rust-random.github.io/rand/rand/distributions/struct.Standard.html) traits. Can I somehow use these to accomplish what I am trying to do?

It might be better if I tell you more broadly what I am doing. I am trying to implement an Autograder for Rust code. The goal is to create some trait Samplable such that if any custom struct implements it, we can generate random versions of that struct. Then I would like to implement logic on types that implement said trait, e.g.

use rand::thread_rng;
trait Testable<I: Samplable> {
    fn get_test_inputs(&self, num_samples: usize) -> Vec<I> {
        return Standard
            .sample_iter(&mut thread_rng())
            .take(num_samples)
            .collect();
    }
   // other logic for unit tests
}

Currently this throws the exception

the trait `rand::distributions::Distribution<I>` is not implemented for `rand::distributions::Standard`

But perhaps there is some way to "promise" the compiler that Distribution<I> will be implemented for Standard?

The restriction you run into is called orphan rules. They were relaxed in the latest rust version, and I didn't recheck that when I responded earlier, sorry. It seems T here can still not be a type parameter.

I have never used random distributions in rust, but I had a quick look at the docs of rand to see how it works.

As far as I can tell your trait Samplable is pretty much what Distribution is. You would have to implement Samplable on every type you want to use this with, so why not implement Distribution<YourType> for Standard instead of implementing Samplable?

As for Testable, it's not quite clear to me what you want to implement it on, but know that you can make pretty complex trait bounds, like this is a blanket impl for all T:

impl<T> Testable<I> for T

   where Standard: Distribution<I>
{
...
}

However that doesn't really make sense, because there is no link between the bound and T, so all existing types T would always be testable over I.

What I'm trying to say is that if you have a type param I somewhere, you can require that it implements Distribution for Standard.

If all Samplable would be Testable, it would be something like:

impl<I> Testable for I where Standard: Distribution<I>  
{
...
}

I'm not sure if this answered your question...

1 Like

Oh that actually works perfectly:

trait UnitTest<I>
where
    Standard: Distribution<I>,
{
    fn inputs(&self, num_samples: usize) -> Vec<I> {
        return Standard
            .sample_iter(&mut thread_rng())
            .take(num_samples)
            .collect();
    }
}

I didn't realize you could use where clauses to do that.

1 Like