Can i return a generic struct from function?

1k Views Asked by At

Sorry if the answer to the following is straight forward but i can't seem to understand it.

I need to create a swarm and return the swarm to the main function but i don't know how to return a generic struct such as the 'P2p' struct.

Traits are used for abstracting methods so i cannot declare one to abstract the structs' attributes.

ps: swarm is of type struct ExpandedSwarm<"something that depends on the behaviour and transport">

pps: Thank you for any input.


fn create_swarm<T>() -> Result<T, Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());

    println!("Local peer id --> {}", local_peer_id);

    let transport = block_on(libp2p::development_transport(local_key))?;
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
    let local_swarm = Swarm::new(transport, behaviour, local_peer_id);
    
    let p = P2p::new(local_swarm);

    Ok(p)
}


struct P2p <T> {
    swarm: T
}

impl <T> P2p<T> {
    pub fn new(swarm: T) -> Self {
        return Self{swarm}
    }
}


2

There are 2 best solutions below

0
On

If all of your swarm types implement the same trait, say Swarmer, you can box it, make P2p's swarm type a Box<dyn Swarmer> and return type Result<P2p, Box<dyn Error>>.

Otherwise, you need to think about how to abstract this. Each of the variables in the create_swarm function must have a fixed type known at compile time. That can be some sort of trait object (boxed or otherwise), but you're never going to just be able to return an object of arbitrary type. For example, every instance of transport will need to have a fixed, discernible type, and that will determine the type of local_swarm (assuming it's generic) and hence p.

If you really need an arbitrary object that you can later downcast into the appropriate concrete type, you can use Box<dyn Any> (assuming your types implement Any). This is like C's void *, and can be useful where you need to pass different kinds of opaque data between cooperating functions with some oblivious functions in between. However, you won't really be able to call any sort of Swarm-specific methods on it.

0
On

Generic parameters are chosen by the caller. This is not the case here: since your function creates the swarm itself then the function imposes the return type. Depending on your actual use-case, there are different possible solutions:

If the caller doesn't need to be able to choose the return type

Simply remove the generic and use a constant type for your function:

fn create_swarm() -> Result<P2p<Swarm>, Box<dyn Error>> {

If there are multiple swarm types and the caller must be able to choose at compile-time:

You need a generic way to create a swarm of the appropriate type, which means that you need some kind of SwarmBuilder trait that defines the new function for swarms:

trait SwarmBuilder {
    fn new (t: Transport, b: Behaviour, i: PeerId) -> Self;
}

impl SwarmBuilder for Swarm {
    fn new (t: Transport, b: Behaviour, i: PeerId) -> Self {
        Swarm::new (t, b, i)
    }
}

fn create_swarm<T: SwarmBuilder>() -> Result<P2p<T>, Box<dyn Error>> {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());

    println!("Local peer id --> {}", local_peer_id);

    let transport = block_on(libp2p::development_transport(local_key))?;
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
    let local_swarm = T::new(transport, behaviour, local_peer_id);
    
    let p = P2p::new(local_swarm);

    Ok(p)
}

If there are multiple swarm types and the caller must be able to choose at run-time:

You need a trait that defines the behaviour common to all swarms, and you need to return a boxed trait object instead of an explicit type:

fn create_swarm() -> Result<P2p<Box<dyn SwarmTrait>>, Box<dyn Error>> {