gextra
gextra

Reputation: 8913

how to create a function that accepts any struct type

The following code works... but ... I am looking for a generic way to setup a channel receiver in a way that I can transmit any struct type value

The following async function setup returns a tx object and at compile time it understands it is of type CIRCLE, and the inner receiver (rx) will expect CIRCLEs... although I´d want to write a generic function that can receive any sort of object... say RECTANGLES, CIRCLES, etc... Is this possible with Generics ?

use serde::__private::de::IdentifierDeserializer;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::sync::mpsc;

#[derive(Debug)]
pub struct Shape {
    pub id: Option<String>,
}

#[derive( Debug)]
pub struct Circle {
    pub id: Option<String>,
    pub radius: f32,
}

#[derive(  Debug)]
pub struct Rectangle {
    pub id: Option<String>,
    pub width: f32,
    pub height: f32,
}

// QUESTION: telling compiler that TX will be of Circle type but how to make it work for also Rectangle ?
pub async fn setup() -> mpsc::Sender<Circle> {
    let (tx,  mut rx) = mpsc::channel(1);
    let handle = tokio::spawn(async move {
        while let Some(my_shape) = rx.recv().await {
            println!("... received a shape: {:?}", my_shape);
        };
    });
    tx
}

#[tokio::main]
async fn main() {
    let tx = setup().await;
    let my_shape = Circle{id: None, radius: 2.0};
    // let my_shape = Rectangle{id: None, width: 3.0, height: 4.0};   <== WILL NOT WORK
    let handle = tokio::spawn(async move {
        let _ =  tx.send(my_shape).await;
    });
    loop {};
}

Upvotes: 0

Views: 277

Answers (1)

Finomnis
Finomnis

Reputation: 22838

If you want to have several types that all have some common functionality, you want to bundle the common functionality into traits.

Then, instead of telling the channel to transport specific objects, you can tell the channel to transport all objects that implement said trait.

The only thing you have to do additionally is to Box the types. By telling the channel to transport objects of a specific trait, Rust looses the information about how big those objects are, because it could be anything. Therefore, by packaging them in a Box (meaning: putting them behind one pointer indirection on the heap) it doesn't matter for the compiler how big they are, because it only handles the Box item that references it, and Boxes are always identical in size. (They don't contain the data, they only point to it somewhere in the heap).

If applied to your code, it could look like this:

use std::fmt::Debug;
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

pub trait Shape: Debug + Send + Sync {
    fn get_id(&self) -> Option<String>;
}

#[derive(Debug)]
pub struct Circle {
    pub id: Option<String>,
    pub radius: f32,
}

impl Shape for Circle {
    fn get_id(&self) -> Option<String> {
        self.id.clone()
    }
}

#[derive(Debug)]
pub struct Rectangle {
    pub id: Option<String>,
    pub width: f32,
    pub height: f32,
}

impl Shape for Rectangle {
    fn get_id(&self) -> Option<String> {
        self.id.clone()
    }
}

pub async fn setup() -> mpsc::Sender<Box<dyn Shape>> {
    let (tx, mut rx) = mpsc::channel(1);
    tokio::spawn(async move {
        while let Some(my_shape) = rx.recv().await {
            println!("... received a shape: {:?}", my_shape);
        }
    });
    tx
}

#[tokio::main]
async fn main() {
    let tx = setup().await;

    // let my_shape = Rectangle{id: None, width: 3.0, height: 4.0};   <== WILL NOT WORK
    tx.send(Box::from(Circle {
        id: None,
        radius: 2.0,
    }))
    .await
    .unwrap();
    tx.send(Box::from(Rectangle {
        id: Some("my_id".to_string()),
        width: 10.0,
        height: 15.3,
    }))
    .await
    .unwrap();

    sleep(Duration::from_millis(100)).await
}
... received a shape: Circle { id: None, radius: 2.0 }
... received a shape: Rectangle { id: Some("my_id"), width: 10.0, height: 15.3 }

Further feedback for your original code:

  • NEVER use endless loops without any kind of await in them in async programming. This will block the event loop, which stops everything. Always at least put a sleep(..).await in there.
  • I removed the serde imports because they had nothing to do with the question on hand. Also, probably don't use serde::__private, because it's not meant to be used by the end-user.
  • No need to create the handle variables if you don't use them.
  • Don't hide errors via let _ = . Handle them, maybe with unrwap(), but at least with an error message. Completely hiding then can cause a lot of frustration when searching for problems.

Upvotes: 2

Related Questions