1

As answered by the ever so helpful and ubiquitous Shepmaster, could someone help me with a syntax hurdle I'm encountering? In the previous answer, Strategy::execute() and Context::do_things() returns ().

How does one implement if a generic type is returned? Or am I missing some fundamental perspective in Rust?

I tried the following code but am currently stuck at:


struct Context<S> {
    strategy: S,
}

impl<S> Context<S>
where
    S: Strategy,
{
    fn do_things(&self) -> T {
        println!("Common preamble");
        self.strategy.execute()
    }
}

trait Strategy<T> {
    fn execute(&self) -> T;
}

struct ConcreteStrategyA;

impl Strategy<AStruct> for ConcreteStrategyA {
    fn execute(&self) -> AStruct {
        println!("ConcreteStrategyA");
        AStruct::default()
    }
}

struct AStruct {
    id: u32,
}

impl Default for AStruct {
    ...
}

struct ConcreteStrategyB;

impl Strategy<BStruct> for ConcreteStrategyB {
    fn execute(&self) -> BStruct {
        println!("ConcreteStrategyB");
        BStruct::default()
    }
}

struct BStruct {
    id: u32,
}

impl Default for BStruct {
    ...
}

I have no idea where to put T for Context::do_things() -> T.

I looked around but some other samples return () as well.

Online tinkering

Thanks for reading.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
MikeTheSapien
  • 161
  • 3
  • 8
  • 1
    I can't be sure without knowing more about what you're trying to do, but I suspect that in your case `T` should be an [associated type](https://doc.rust-lang.org/stable/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) rather than a generic parameter: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ce0687aef7b4574f53d878d166d8b0eb). – Jmb Sep 17 '21 at 06:43
  • Oh wow this is another great answer! Thanks for this as well! I think it's better if you post your comment as an answer? But not really sure for now with SO etiquette or something. – MikeTheSapien Sep 17 '21 at 06:48
  • Regarding to what I'm doing, I'm trying to see if Strategy Pattern fits in my HTTP Client calls. Different APIs with possibly different "treatment", different URLs, and different HTTP Response types, but I'm still not sure. Still currently reading, watching, listening everything about OOP and trying to fit it in my codes. – MikeTheSapien Sep 17 '21 at 06:58

2 Answers2

2

Depending on what you're trying to do, it might be better to use an associated type instead of a generic. When choosing between associated types and generic parameters, you should use a generic if the caller can choose the type to use. You should use an associated type if the implementation determines the type.

Although there are exceptions (e.g. Into::into), most of the time if a type appears only in the return of a method, then this is a good indication that it should probably be an associated type. OTOH if a type is used for a parameter, then there is a fair chance that it should be a generic.

In your case, using an associated type would look like this:

struct Context<S> {
    strategy: S,
}

impl<S> Context<S>
where
    S: Strategy,
{
    fn do_things(&self) -> S::Output {
        println!("Common preamble");
        self.strategy.execute()
    }
}

trait Strategy {
    type Output;
    fn execute(&self) -> Self::Output;
}

struct ConcreteStrategyA;

impl Strategy for ConcreteStrategyA {
    type Output = AStruct;
    fn execute(&self) -> AStruct {
        println!("ConcreteStrategyA");
        AStruct::default()
    }
}

#[derive (Default)]
struct AStruct {
    id: u32,
}

struct ConcreteStrategyB;

impl Strategy for ConcreteStrategyB {
    type Output = BStruct;
    fn execute(&self) -> BStruct {
        println!("ConcreteStrategyB");
        BStruct::default()
    }
}

#[derive (Default)]
struct BStruct {
    id: u32,
}

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
0

I think using Strategy<T> more often is the key:

impl<S, T> Context<S, T>
where
    S: Strategy<T>,
{
    fn do_things(&self) -> T {
        println!("Common preamble");
        self.strategy.execute()
    }
}

See here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a3a14895e22ca57f5f96c855b7046257

phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • I feel stupid! I tried that solution as well, but then my problem was raised into `struct Context`. A *help message* showed in the compiler: ```help: consider removing `T`, referring to it in a field, or using a marker such as `std::marker::PhantomData` help: if you intended `T` to be a const parameter, use `const T: usize` insteadrustc(E0392)```, but was afraid as it might lead to unmanageable complexity on my part I was going at it for hours now and was afraid to defer the problem to another set of incomprehensible syntax. – MikeTheSapien Sep 17 '21 at 06:41
  • Thanks for this answer. Now I feel high and invincible again! Til' my next frustrated hours again! – MikeTheSapien Sep 17 '21 at 06:45