So I have a struct WeatherStation
which consists of a name: String
and probability distribution function which should implement the rand::distr::Distribution
trait. It look something like this:
struct WeatherStation<'a, D> {
name: &'a str,
distribution: D,
}
I also have 2 methods on that struct:
impl<'a, D> WeatherStation<'a, D> {
fn new(name: &'a str, distribution: D) -> Self {
Self {
name,
distribution,
_marker: PhantomData
}
}
fn measurement<T>(&self) -> T
where
T: Float,
D: Distribution<T>
{
self.distribution.sample(&mut rand::rng())
}
}
My question is how should I best enforce these trait bounds.
Here are my ideas:
new
method):
struct WeatherStation<'a, D> {
name: &'a str,
distribution: D,
}
impl<'a, D> WeatherStation<'a, D> {
fn new(name: &'a str, distribution: D) -> Self {
Self {
name,
distribution,
}
}
fn measurement<T>(&self) -> T
where
T: Float,
D: Distribution<T>
{
self.distribution.sample(&mut rand::rng())
}
}
struct WeatherStation<'a, D> {
name: &'a str,
distribution: D,
}
impl<'a, D> WeatherStation<'a, D> {
fn new<T>(name: &'a str, distribution: D) -> Self
where
T: Float,
D: Distribution<T>
{
Self {
name,
distribution,
}
}
fn measurement<T>(&self) -> T
where
T: Float,
D: Distribution<T>
{
self.distribution.sample(&mut rand::rng())
}
}
PhatomData
, no relationship between D and T in struct):
struct WeatherStation<'a, D, T> {
name: &'a str,
distribution: D,
_marker: PhantomData<T>
}
impl<'a, D, T> WeatherStation<'a, D, T>
where
D: Distribution<T>,
T: Float
{
fn new(name: &'a str, distribution: D) -> Self {
Self {
name,
distribution,
_marker: PhantomData
}
}
fn measurement(&self) -> T {
self.distribution.sample(&mut rand::rng())
}
}
Which one of these should I use and why? Is there a more idiomatic way to do this?
Your "idea 1" is the most idiomatic Rust. Note in particular that the standard library takes this approach -- for example, HashMap::new
does not require any particular bounds, but anything that could actually fetch/insert will require K: Hash + Eq
.
As far as whether T
should be part of the type, that ultimately depends on factors not apparent from your question. For example, if you are relying on a specific WeatherStation
instantiation to convey information about the type of the distribution that should be used in all cases, then you could do this (though I would use PhantomData<fn() -> T>
as this helps the borrow checker know that your type doesn't actually contain a T
). You could also need to do this in particular trait implementation scenarios, where you need to add another type to be able to differentiate blanket impls.