I am fairly new to rust and am wondering why I need the add a trait bound related to a function called within methods. It feels like spooky-action-at-a-distance and is disconcerting.
I did a little experiment to simulate from an ARMA(P,Q) model. A portion of the code looks like:
pub fn sample_normal<T, R: Rng>(mean: T, sd: T, rng: &mut R) -> T
where
T: Add<Output = T> + Mul<Output = T>,
StandardNormal: Distribution<T>,
{
rng.sample::<T, StandardNormal>(StandardNormal) * sd + mean
}
pub struct AR<const P: usize, T> {
state: ArrayDeque<T, P, Wrapping>,
pars: [T; P],
mean: T,
stdev: T,
}
impl<const P: usize, T> AR<P, T>
where
T: Zero + Copy + Add<Output = T> + Mul<Output = T>,
for<'a> &'a T: Mul<&'a T, Output = T>,
StandardNormal: Distribution<T>, // Why???
{
pub fn new(state: [T; P], pars: [T; P], mean: T, stdev: T) -> Self {
Self {
state: state.into_iter().collect(),
pars,
mean,
stdev,
}
}
pub fn randinit<R: Rng>(pars: [T; P], mean: T, stdev: T, rng: &mut R) -> Self {
let mut res = Self {
state: std::iter::repeat_with(|| sample_normal(Zero::zero(), stdev, rng))
.take(P)
.collect(),
pars,
mean,
stdev,
};
for _ in 0..P * P * 10 {
res.next(rng);
}
res
}
fn update_state(&mut self, state: T) {
self.state.push_front(state);
}
fn project(&self) -> T {
self.state
.iter()
.zip(self.pars.iter())
.fold(Zero::zero(), |sum, x| sum + x.0 * x.1)
}
pub fn next<R: Rng>(&mut self, rng: &mut R) -> T {
let noise = sample_normal(Zero::zero(), self.stdev, rng);
let next = self.project() + noise;
self.update_state(next);
self.mean + next
}
pub fn gen<R: Rng>(&mut self, n: usize, rng: &mut R) -> Vec<T> {
std::iter::repeat_with(|| self.next(rng)).take(n).collect()
}
}
It seems the trait bound is required in any scope calling sample_normal
. I have also seen this where wrapping a container-like object required a trait bound like Allocator: DefaultAllocator<T>
. I don't see why it is not implicit, especially because when working with other crates, these trait bound are often not documented and it requires a lot of guesswork to figure out. At least that's been my experience while stumbling through generics. Isn't it enough to give the trait bound in the function definition? (Edit: I should mention that in my code, sample_normal
is in a different module.)