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.
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?
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>
{
...
}