Reputation: 45
Given this simplified code (which doesn't compile), how I can inform the compiler that, that in the impl
I only wish fn execute
to allow cases where Model::Parameters == P
?
trait Model {
type Parameters;
fn run(&self, p: &Self::Parameters) -> f64;
}
trait Analysis {
fn execute<M: Model>(&self, model: M) -> (M::Parameters, f64);
}
pub struct Data<P>{
pub value: P,
}
impl<P> Analysis for Data<P> {
fn execute<M: Model>(&self, model: M) -> (M::Parameters, f64) {
let p = self.value;
(p, model.run(&p))
}
}
I tried a where
clause:
fn execute<M>(&self, model: M) -> (M::Parameters, f64)
where M: Model<Parameters = P> {
let p = self.value;
(p, model.run(&p))
}
but the compiler rejects it as that deviates from the declaration in the Analysis
trait, which knows nothing about P.
Upvotes: 0
Views: 60
Reputation: 43842
You can't add bounds to a trait method, because that would break the guarantee of Rust's trait system, that generic code will be usable in every case where its bounds are met — “no post-monomorphization errors”. You can add arbitrary bounds to a trait implementation, but that doesn't help here since P
only appears in relation to M
which is not a parameter or associated type of the trait, only its method. The trait's current signature is forcing every implementation to work for all possible Model
s.
If you want to allow an Analysis
to only work for certain kinds of Parameters
, then you have to put that in the trait. One simple possibility is that Analysis
should have an associated type like Model
does, but I'm guessing that you would have thought about it already. Another possibility is to give Analysis
a type parameter, which means that each implementation can choose whether or not it works for different Parameters
. This compiles:
trait Model {
type Parameters;
fn run(&self, p: &Self::Parameters) -> f64;
}
trait Analysis<P> {
fn execute<M: Model<Parameters = P>>(&self, model: M) -> (P, f64);
}
pub struct Data<P> {
pub value: P,
}
impl<P: Clone> Analysis<P> for Data<P> {
fn execute<M: Model<Parameters = P>>(&self, model: M) -> (P, f64) {
let p = &self.value;
(p.clone(), model.run(p))
}
}
// can also write impl<P> Analysis<P> for AnalysisThatDoesntCare {}
(I also had to change the function body to clone self.value
, since execute
returns P
by value.)
Or maybe the Analysis
trait should include the Model
instead, so the trait is generic but the function isn't:
trait Model {
type Parameters;
fn run(&self, p: &Self::Parameters) -> f64;
}
trait Analysis<M: Model> {
fn execute(&self, model: M) -> (M::Parameters, f64);
}
pub struct Data<P> {
pub value: P,
}
impl<M, P> Analysis<M> for Data<P>
where
M: Model<Parameters = P>,
P: Clone,
{
fn execute(&self, model: M) -> (P, f64) {
let p = &self.value;
(p.clone(), model.run(p))
}
}
This version allows the implementations to constrain what Model
s they work with, not just their Parameters
type, and it might be simpler when working with non-generic implementors of Analysis
.
(P.S. Rust API style is to name traits after the operations they enable, not a description of the value, when that's practical. So, possibly your trait should be named Analyze
with method analyze()
(or analyse()
) instead of Analysis
and execute()
.)
Upvotes: 2