Actix/Axiom/Riker - sending/receiving generic types

Hello.
I am trying create communication system with 3 actors graph

The data transmitted is of different types (generic - bool, i8, i16, i32, u8, u16, u32).

I need simple example. Please help.
I would like to use Actix or Axiom.

Tell us a little bit more about what is going on with your data model.
What types are getting sent where?
Are you trying to send multiple types down the same channel?
Do you need to know the concrete type on the receiving end, or is a trait object ok?

Thx for reply.

What types are getting sent where?

bool, i8...i32, u8..u32.

Are you trying to send multiple types down the same channel?

Yes.

Do you need to know the concrete type on the receiving end, or is a trait object ok?

Yes. I like it at Axiom. This is done simply and does not require many lines of code.
It is not necessary to wrap values with a Message structure.

For example:

aid.send_new(17).unwrap();

It looks like the way Axiom is doing it is by wrapping messages in a message struct, but using some clever impls to do the wrapping for you.

Using stdlib versions of its traits, it would look something like this:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=37401d103fde056281ca7e71b682ffda

Axiom just uses its own traits to get rid of ergonomic concerns.

1 Like

How to change example for support generic types?

use actix::prelude::*;

/// Define `Ping` message
//struct Ping(usize);

#[derive(Debug, Clone)]
struct Ping<T> {
    x: T
}

impl<T> Ping<T> {
    fn x(&self) -> &T {
        &self.x
    }
    fn new() -> Ping
    {
        Ping{x : 0}
    }
}


impl Message for Ping {
    type Result = usize;
}

/// Actor
#[derive(Debug, Clone)]
struct MyActor {
    count: usize,
}

/// Declare actor and its context
impl Actor for MyActor {
    type Context = Context<Self>;
}

/// Handler for `Ping` message
impl Handler<Ping> for MyActor {
    type Result = usize;

    fn handle(&mut self, msg: Ping, _: &mut Context<Self>) -> Self::Result {
        self.count += msg.0;
        println!("MyActor: {:?}", self);

        self.count
    }
}

#[actix_rt::main]
async fn main() {
    // start new actor
    let actor = MyActor { count: 10 };
    println!("MyActor: {:?}", actor);

    let addr = actor.start();

    // send message and get future for result
    let res = addr.send(Ping::<u32>::new()).await;

    // handle() returns tokio handle
    println!("RESULT: {}", res.unwrap() == 20);

    // stop system and exit
    System::current().stop();
}

All generic: playground

Note that if you wanted to keep the usize on the MyActor, you could but then you can only pass types to it which implement AddAssign<usize>, which I haven't checked but I assume is just usize.

I hate to break it to you, but I think if you don't have the hang of generics and trait bounds, or if you find you are not understanding most errors the compiler throws at you, I would strongly suggest you work through the learning materials on https://www.rust-lang.org/learn like the book, before trying to use an actor library, because you are going to sweat every letter you type in.

2 Likes

Thx for reply. :slightly_smiling_face:

I have a different problem. How to make cross reference in Rust?
I have two actors and I want them to be able to exchange data with each other.
How to do it?

actor

Your answer is half in the ping example above. The other half is:
Give the address of DataHub to Driver and the other way around. Now they have each other's address, they can send messages to each other.

1 Like

I have a problem because I can't provide the address.

let mut data_hub = DataHub::new();
let addr = data_hub.start();

let driver = Driver::new(addr);
let addr_drv = driver.start();

data_hub.register_driver("driver_1".to_string(), addr_drv); //error: value borrowed here after move

Yes, please read the chapter about ownership in the book. It will explain you why that is.

data_hub.start(); will take ownership of data_hub and will never, ever let you use it again. :sob:

You can check some of the fundamentals of the actor model as to why this is.

So in this case, you will have to create a Message that holds the address, so you can send it to data_hub like that. Hint, that probably means data_hub will have to store it as Option< Addr<Driver> >, because it will have to be created before it has the address. But that's a bit of a nuisance, because you will have to match the option everytime you want to use it.

It's a limitation of the actix API that you can't obtain an address to an actor before it's started. Only during construction can you do it with Actor::create, but using that to work around this issue is also a bit messy, albeit possible:

use actix::prelude::*;

struct DataHub
{
   driver_addr: Addr<Driver>,
}

impl Actor for DataHub { type Context = Context<Self>; }

struct Driver
{
   data_hub_addr: Addr<DataHub>,
}

impl Actor for Driver { type Context = Context<Self>; }


fn main()
{
   let mut data_hub_addr = None;

   let driver_addr = Driver::create( |driver_ctx: &mut Context<Driver>|
   {
      let driver_addr = driver_ctx.address();

      data_hub_addr = DataHub::create( |_ctx: &mut Context<DataHub>|
      {
         DataHub{ driver_addr }

      }).into();

      Driver{ data_hub_addr: data_hub_addr.clone().expect( "data_hub_addr to be set" ) }
   });

   let data_hub_addr = data_hub_addr.expect( "data_hub_addr to be set" );

   // you can use both data_hub_addr and driver_addr here to send them messages.
}

1 Like

Very thx for reply.

I found helpful YT film : Tensor - Live Coding with Rust and Actix

My repo : Github

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.