Abstract trait troubles

I'm doing some statistical things using the statrs crate. It implements a number of distribution functions and my input can be one of them, and not always the same.

I would like to have something that in pseudo-code looks like this:

let curve = create_curve (from parameters);
curve.sample(some point);
curve.pdf(some point);

Unfortunately, the two functions I need, sample() and pdf() are implemented in two different traits (Distribution and Continuous) and rust can't return multiple traits. So I've been wrecking my brain trying to figure out a way to do this kind of abstract handling. It gets a bit more tricky as both the sample() and the pdf() are inside their own seperate functions because I do additional processing, so I also need to pass the curve into them.

My (non-functioning) code is like this:

extern crate statrs;
extern crate rand;

use statrs::distribution::{Beta, LogNormal, Continuous};
use statrs::statistics::Variance;
use rand::distributions::Distribution;

pub fn main(dist: &str, min: f64, max: f64) -> f64 {
    let curve = create_curve();

    curve_value(&curve, min, max);
}

fn create_curve(dist: &str) -> impl Distribution<f64> + Continuous<f64, f64> {
    match dist {
        "lognorm"   => lognorm_from_pert(),
        _           => beta_from_pert()
    }
}

fn beta_from_pert() -> impl Distribution<f64> + Continuous<f64, f64> {
    Beta::new(1.5, 0.5).unwrap()
}


fn lognorm_from_pert() -> impl Distribution<f64> + Continuous<f64, f64> {
    LogNormal::new(1.5, 0.5).unwrap()
}


fn curve_value(curve: &impl Distribution<f64>, min: f64, max: f64) -> f64 {
    curve.sample(&mut rand::thread_rng()) * ( max - min) + min
}

The basic idea is that I want to have a code that goes "ok, here is a curve, it has these traits and I'll use them without worrying which type of curve it is". The exact thing that traits should do, but the multiple traits thing is making it impossible.

It looks like you need more redirection. You might define a trait like:

trait Curve: Distribution<f64> + Continuous<f64, f64> {}

You can make a blanket impl for all types that implement both super traits. Now implementors guarantee to support both behaviors. I don't know if your types do however.

Otherwise you can create a struct/enum Curve which will abstract out the differences, like returning an Option for methods that might not be available etc.

I found that somewhere and tried it as well, could not get it to work.

Is there a working example of something like this? All I find on the Internet is basically "do this", but never the whole thing with parameter passing and all.

I'll make a playground:

Note that the method returns a Box as impl Trait in return type always has to return the exact same type, and I suppose that if you had only one type, you wouldn't be asking this. If you don't want to pay for dynamic dispatch, I suppose you'd be limited to something like enum_dispatch.

1 Like

You can let your code be Rust-syntax highlighted:

let curve = create_curve (from parameters);
curve.sample(some point);
curve.pdf(some point);

See Mapping of Undefined Behavior to Source Code in Rust - #2 by RustyYato

When I try to adapt this, I get an error:

the trait Both cannot be made into an object

maybe I adapted it badly. I'm still largely ignorant in rust:

trait Both: Distribution<f64> + Continuous<f64, f64> {}

impl<T> Both for T where T: Distribution<f64> + Continuous<f64, f64> {}


fn curve( which: &str ) -> Box< dyn Both >
{
    match which
    {
        "A" => Box::new( Beta::new(1.5, 0.5) ),
        "B" => Box::new( LogNormal::new(1.5, 0.5) ),
        _   => panic!( "Unknown type provided" ),
    }
}

fn main()
{
    let curve = curve( "A" );

    // you can call methods from both Sample and Pdf on curve here.
}

I may have to be more specific:

use statrs::distribution::{Beta, LogNormal, Continuous};
use rand::distributions::Distribution;

The function sample() is defined on the Distribution trait, while the function pdf() is defined on the Continuous trait. That's basically the source of all my troubles.

It means one of your traits is not object safe...

I also now see that these traits come from completely different crates. I would personally make a facade to this, like a struct/trait which has all the methods you want to use, and which hides the specifics of the libraries you use. It seems hard to know for sure that a type implements unrelated traits from two different crates.

Make your own type and a potentially a trait that has all the methods you want to use. Then impl that trait for your type. This way you completely control the situation and your trait will be object safe if you want/need it to be. Also, the compiler will be able to guide you better.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.