Есть ли способ создать структуру на основе реализации признака?

Я пытаюсь использовать структуру с несколькими реализациями одного метода:

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();
}

Что вернется:

>>> [1,4,9];
>>> [3,4,5];

Я знаю, что могу создать две структуры, реализующие эти методы по-разному, но это неправильно, поскольку потенциально это много избыточного кода. Я также знаю, что мог бы иметь ссылку на этот метод реализации:

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);
}

Этот подход кажется излишне сложным, когда я хочу использовать множество аргументов внутри данной структуры:

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, ...);
}

Похоже, это проклятый способ написания кода. Что происходит, когда реализация метода не использует параметр, например m, а другие его используют?


person Daniel Zdancewicz    schedule 04.11.2020    source источник
comment
Вы хотите реализовать Trait для чего-нибудь, кроме Bar<X>? Если нет, просто реализуйте его для X и заставьте apply() принимать &Bar вместо &self.   -  person Sven Marnach    schedule 04.11.2020


Ответы (2)


Черты характера используются для определения общего поведения. В вашем примере вы хотите реализовать одну и ту же черту разными способами. Это противоречит назначению черты характера. Вместо того, чтобы иметь две структуры, как вы пытались, у вас, вероятно, должны быть две черты:

trait ThisWay {
    fn apply(&self) -> Vec<usize>;
}

trait ThatWay {
    fn apply(&self) -> Vec<usize>;
}

Теперь вы можете реализовать оба трейта для своей структуры:

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()
    }
}

Поскольку Bar реализует ThisWay и ThatWay, теперь у него есть два определения для метода apply. Чтобы устранить неоднозначность между ними, мы должны использовать полностью квалифицированный синтаксис:

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));

И, как и ожидалось, вы получите два разных результата:

Method: [1, 4, 9]
Method: [3, 4, 5]
person Ibraheem Ahmed    schedule 04.11.2020
comment
Я знал, что у моей проблемы есть простое решение, спасибо, ты здесь мой спаситель: D - person Daniel Zdancewicz; 04.11.2020
comment
@DanielZdancewicz Нет проблем ???? - person Ibraheem Ahmed; 04.11.2020

В качестве альтернативы другому ответу вы также можете использовать что-то более похожее на ваш исходный пример, используя универсальные типы и типы структур нулевого размера в качестве маркеров того метода, который вы хотите использовать. Вот полный пример:

// 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();
}

При запуске вывод правильно отражает различные используемые функции:

Method: [1, 4, 9]
Method: [3, 4, 5]

Этот подход имеет семантику, отличную от другого ответа: в то время как другой ответ позволяет вам создать Bar, который может использоваться с обеими функциями, этот подход ограничивает вас одной реализацией на уровне типа, поскольку Bar<ThisWay> и Bar<ThatWay> - это два отдельных типа, которые каждый обеспечивает только одну apply функцию. Это может быть желательно для безопасности типов в некоторых сценариях, но может быть не тем, что вам нужно в этом конкретном случае.

// 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]
person apetranzilla    schedule 04.11.2020
comment
Спасибо, я тоже подумал, как использовать PhantomData, ура - person Daniel Zdancewicz; 04.11.2020