Reputation: 11
I'm trying to use a struct with multiple implementations of one method:
trait Trait { fn apply(&self) -> vec<usize>; }
struct Bar<X> { vec: Vec<usize> }
impl<X> Bar<X> {
pub fn new(vec: Vec<usize>) -> Self { Self{vec} }
pub fn test(&self) {
// Things here
println!("Method: {:?}", self.apply());
// Things there
}
}
impl Trait for Bar<ThisWay> {
fn apply(&self) -> Vec<usize> { self.vec.iter().map(|x| x.pow(2)).collect() }
}
impl Trait for Bar<ThatWay> {
fn apply(&self) -> Vec<usize> { self.vec.iter().map(|x| x + 2).collect() }
}
fn main() {
Bar<ThisWay>::new(vec![1,2,3]).test();
Bar<ThatWay>::new(vec![1,2,3]).test();
}
Which would return:
>>> [1,4,9];
>>> [3,4,5];
I know I could create 2 structs implementing these methods differently, but that feels wrong as it's potentially a lot of redundant code. I also know I could have a reference to that implementation method:
trait Trait { fn apply(vec: &Vec<usize>) -> Vec<usize>; }
impl Struct{
// fn new
test(&self, t: &impl Trait) {
// Things here
println!("{:?}", t::apply(&self.vec));
// Things there
}
}
struct ThisWay;
struct ThatWay;
impl Trait for ThisWay {fn apply(vec: &Vec<usize>) -> Vec<usize> {///} };
impl Trait for ThatWay {fn apply(vec: &Vec<usize>) -> Vec<usize> {///} };
fn main() {
let this_way = ThisWay{};
let that_way = ThatWay{};
let problem = Bar::new(vec![1,2,3]);
problem.test(&this_way);
problem.test(&that_way);
}
This approach seems needlessly complicated when I would want to use many arguments inside given struct:
fn hill_climber(&self, nullary_op: &impl NullaryOperator, unary_op: &impl UnaryOperator, ...) {
self.vec = nullary_op();
self.vec = unary_op(&self.vec, self.n, self.m, self.jobs, self.stuff, ...);
}
This seems to be a cursed way of writing code. What happens when a method implementation doesn't use a parameter e.g m
, and other uses that?
Upvotes: 0
Views: 365
Reputation: 5939
As an alternative to the other answer, you can also use something more similar to your original example, using generics and zero-sized struct types as "markers" for which method you want to use. Here's a complete example:
// PhantomData allows us to "use" a generic without having an actual field
use std::marker::PhantomData;
// These structs will be used to indicate which implementation we want
struct ThisWay;
struct ThatWay;
trait Trait { fn apply(&self) -> Vec<usize>; }
struct Bar<X> {
vec: Vec<usize>,
// This extra field is here to stop the compiler complaining about not using X
_marker: PhantomData<X>,
}
impl<X> Bar<X> {
pub fn new(vec: Vec<usize>) -> Self { Self { vec, _marker: PhantomData } }
// Note the new "where" clause here - we can only implement this function if Bar<X> implements Trait
pub fn test(&self) where Self: Trait {
// Things here
println!("Method: {:?}", self.apply());
// Things there
}
}
impl Trait for Bar<ThisWay> {
fn apply(&self) -> Vec<usize> { self.vec.iter().map(|x| x.pow(2)).collect() }
}
impl Trait for Bar<ThatWay> {
fn apply(&self) -> Vec<usize> { self.vec.iter().map(|x| x + 2).collect() }
}
fn main() {
Bar::<ThisWay>::new(vec![1,2,3]).test();
Bar::<ThatWay>::new(vec![1,2,3]).test();
}
Running this, the output correctly reflects the different functions being used:
Method: [1, 4, 9]
Method: [3, 4, 5]
This approach has different semantics from the other answer: whereas the other answer allows you to construct a Bar
that's capable of being used with both functions, this approach locks you into one implementation at the type level, since Bar<ThisWay>
and Bar<ThatWay>
are two separate types that each only provide only one apply
function. This can be desirable for type safety in some scenarios, but may not be what you need for this particular case.
// since x is declared as `Bar<ThatWay>`, we can only ever use the `ThatWay` implementation of `apply`/`test`
let x: Bar<ThatWay> = Bar::new(vec![1, 2, 3]);
x.test(); // -> Method: [3, 4, 5]
Upvotes: 0
Reputation: 13518
Traits are used to define shared behaviour. In your example, you want to implement the same trait in different ways. This goes against the purpose of a trait. Instead of having two structs as you tried, you should probably have two traits:
trait ThisWay {
fn apply(&self) -> Vec<usize>;
}
trait ThatWay {
fn apply(&self) -> Vec<usize>;
}
Now you can implement both traits for your struct:
struct Bar {
vec: Vec<usize>,
}
impl ThisWay for Bar {
fn apply(&self) -> Vec<usize> {
self.vec.iter().map(|x| x.pow(2)).collect()
}
}
impl ThatWay for Bar {
fn apply(&self) -> Vec<usize> {
self.vec.iter().map(|x| x + 2).collect()
}
}
Because Bar
implements ThisWay
and ThatWay
, it now has two definitions for the apply
method. To disambiguate between them, we have to use Fully Qualified Syntax:
let this_bar = Bar::new(vec![1, 2, 3]);
println!("Method: {:?}", <Bar as ThisWay>::apply(&this_bar));
let that_bar = Bar::new(vec![1, 2, 3]);
println!("Method: {:?}", <Bar as ThatWay>::apply(&that_bar));
And, as expected, you get two different outputs:
Method: [1, 4, 9]
Method: [3, 4, 5]
Upvotes: 2