How to construct dependency graph including nodes with input and output type

I am trying to build a program that users can create and share strategies.
Special functionality here is that, strategies can get inputs and return outputs. That is, input and output type is not fixed.

trait Strategy {
  type Input;
  type Output;
  fn logic(&self, input: Self::Input) -> Self::Output;
}

After getting strategies, I want to connect them to build a workflow, which can be represented as graph.
One more thing I want to do is to check if connected input and output are same.

struct Edge<I, M, O> {
  from: Box<Strategy<Input=I, Output=M>>,
  to: Box<Strategy<Input=M, Output=O>>,
}

Edge connects strategies, and actually Edge can also be a Strategy.

impl<I, M, O> Strategy for Edge<I, M, O> {
  type Input = I;
  type Output = O;
  fn logic(&self, input: Self::Input) -> Self::Output {
    self.to.logic(self.from.logic(input))
  }
}

Now I want to handle this strategies and edges in a struct, say Graph.
Then I will sort the graph and run the strategy with No dependency first.
(Going forward, I want to modify the strategy, so that it can get more than one inpu.t)
However, as they have generic types, it is not possible to store them in a data structure like Vec.
I wonder if there is a good way to handle this kind of approach.

Thanks in advance!

More precisely, it's because each implementation of a generic is a different type and may have different size. The trick is to store boxed element in the vector:

use std::fmt::Display;

fn main(){
    //Vector containing boxed element implementing Display trait
    let mut my_vec = Vec::<Box< dyn Display>>::new();
    my_vec.push(Box::new('c'));
    my_vec.push(Box::new(42u16));
    my_vec.push(Box::new("A boxed str"));
    for e in my_vec {
        println!("{}",e);
    }
}

 also encourage you to look inside std::collections, for example `HashMap` and `BtreeMap` seems better to me to store a graph (but i'm not used to graph)

Building a graph where each node is a strategy implies that edges will have to share the nodes, so you can't use a unique/non-shared pointer like Box in the definition of Edge. You could experiment with using a shared ownership pointer type like Rc or Arc instead, although this will result in memory leakage when your graph contains cycles.

A side note: the use of a bare trait name like Strategy<...> in type context to indicate a trait object type is strongly deprecated in recent Rust and banned entirely in the 2021 edition. You should add the dyn keyword whenever you want to refer to a trait object type, e.g.

struct Edge<I, M, O> {
    from: Rc<dyn Strategy<Input = I, Output = M>>,
    to: Rc<dyn Strategy<Input = M, Output = O>>,
}

Thanks for the reply!

Besides the Rc, dyn mistakes, it still looks impossible to contain every Edges in one Vec(or any collection).
If I have Edge<i32, i32, String> and Edge<i32, String, i32>, then I cannot store them as one Rc<dyn Edge>.
Is there any workaround for this approach?

Thanks a lot!

Agree, as Input and Output types are not fixed, rust does not allow to store them in one trait object.
I wonder if there is no way other than using a concrete type, for Input and Output, to handle all Edges(or Strategies) in one collection.

If you are building graphs that contain different kinds of data known only at runtime, then dyn Trait is most likely the wrong kind of abstraction. You may want to create (an) enum(s) or otherwise tag your data in a way that makes sense for your application.

With only the information you have provided, it's only possible to guess at what design would be best for you. Software architecture generally requires a fairly deep analysis of the requirements and constraints to be met.

Got it, I also thought about the enum, but have one problem.
As enum variants are treated as one type, it seems hard to check if the input and output types are matched.
Let's say I use enum for Input and Output;

enum Data {
  Int(i32),
  String(String),
}

If one strategy returns Data::String and another gets Data::String, is it possible to check if they match in compile time?

One more point is that, program can know the types in compile time.

let e1: Edge<i32, i32, i32> = ...;
let e2: Edge<String, i32, i32> = ...;

let edges: Vec<??> = ...;

The problem is that I cannot handle them as same type of object.
I think I can achieve the goal by introducing other variables and checking them in runtime, but I wonder if there is a good way in the type system of rust.

You need to compare "discriminant" of enum. you can get the disciminant with std::mem::discriminant (i just found it)

No,"Data::String" is a variant, it's only known at runtime
You questions about enum are confusing, are ok with that ?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.