Communicating between objects

I am playing around with websockets (using ws )

I would like to be able to have a server with state (I can keep the state separate so I do not need to mutate the server). When that state changes it is communicated to the clients.

The problem I am having is my server MyFactoryServer cannot keep a reference to the Handlers it creates, and the main programme cannot keep a reference to each of its handlers.

let wss = ws::WebSocket::new(my_server).unwrap(); moves the MyFactoryServer object placing it out of reach, and MyFactoryServer.connection_made surrenders ownership of the Handler, placing it out of reach.

All I can think of are very complicated solutions, but there must be a easier, of not easy, way to achieve this. I need a way to maintain communication with a object that has moved out of scope.

extern crate ws;
use std::thread;
use std::thread::sleep;
use std::time::Duration;

struct State {
    state:char
}
struct MyFactoryServer;
impl MyFactoryServer {
    fn send_state(&self, state: &State) {
	println!("{}", state.state);
    }
}
struct MyFactoryClient;
struct MyHandlerServer{out:ws::Sender}
struct MyHandlerClient{out:ws::Sender}
impl ws::Handler for MyHandlerServer {
    fn on_message(&mut self, msg:ws::Message) -> ws::Result<()> {
        println!("Server got message '{}'. ", msg);
        self.out.send(format!("Client sending ").as_str())
    }
    fn on_close(&mut self, _: ws::CloseCode, _: &str) {
        self.out.shutdown().unwrap();
    }
}
impl ws::Handler for MyHandlerClient {
    fn on_message(&mut self, msg:ws::Message) -> ws::Result<()> {
        println!("Client got message '{}'. ", msg);
        self.out.shutdown()
    }
}
impl ws::Factory for MyFactoryServer {
    type Handler = MyHandlerServer;
    fn connection_made(&mut self, output: ws::Sender) -> Self::Handler{
        MyHandlerServer {out:output}
    }
}

impl ws::Factory for MyFactoryClient {
    type Handler = MyHandlerClient;
    fn connection_made(&mut self, output: ws::Sender) -> Self::Handler{
        output.send("Hello Websocket").unwrap();
	MyHandlerClient {out:output}
    }
}

fn main () {

    let my_server = MyFactoryServer;
    let wss = ws::WebSocket::new(my_server).unwrap();
    let s_thread = thread::spawn(move || {
	wss.listen("127.0.0.1:3012").unwrap()
    });
    // Give the server a little time to get going
    sleep(Duration::from_millis(100));
    // my_server.send_state(&State{state:'c'});
    
    let c_thread = thread::spawn(move || {
        ws::connect("ws://127.0.0.1:3012", |out| {
            out.send("Message from client").unwrap();
            move |msg| {
                println!("Client got message '{}'. ", msg);
                out.close(ws::CloseCode::Normal)
            }

        }).unwrap()

    });    
    let _ = s_thread.join();
    let _ = c_thread.join();
    println!("All done.")
}

I know you are not using async/await, but you may still be able to get some inspiration from this blog post.

2 Likes

Wow Alice. I think that is the most beautiful Rust I have seen since "Hello world".
So easy on the eyes, no syntactic clutter of lifetimes, generics or even traits.
But so cunning, splitting the actor into two structs and a free function.

The reason I'm gushing about it is that I was just starting to contemplate reorganising big mess of Rust, itself derived from a big mess of C#, and making it adabltable to various input and output interfaces: Serial port, raw socket, message que, whatever.

My preferred way to think of that is as a net of processes connected by channels. With various, selectable, input and output nodes. A chain of processing nodes in the middle. And the whole thing replicated and parallelized as many times as we have different data streams to handle.

Back in the day we called that Communicating Sequential Processes (CSP). Or it looks a lot like what Allen Kay had in mind when he point the phrase "Object Oriented Programming" with SmallTalk. (Always wondered why the OOP guys talked about "messages" when what they were actually doing was simply passing parameters to functions that happened to be methods, a totally different thing)

Seems somebody renamed it "Actors" along the way. Who knew?

So my mind turned to Tokio and channels.

Anyway, a big thanks for that heads up, just at the right moment!

1 Like

Thanks! I'm glad you liked it.

As I understand it, actors is not just a new name for CSP, rather, an actor is a specific design pattern that involves channels, whereas CSP is a wider concept that involves both actors as well as other things that use channels.

The comparison to OOP is also quite interesting, because actors is an alternative to trait objects for implementing dynamic dispatch. By writing your code such that multiple handles of the same type could hold a sender connected to different actor implementations, you have what is basically dynamic dispatch. I've seen people use this for a server that needs to support both TcpStream and UnixStream for a connection, but the rest of the code doesn't care about which one it is. They did this by just spawning a different run_my_actor loop in each situation, but reusing the same handle and message type, hiding the difference from any code that uses the handle.

1 Like

Having never read much about "Actor Model" I don't really know.

Back in the day when we had Transputers and the Occam language it was "common knowledge" that it was inspired by CSP. Occam had syntax and semantics for processes and channels baked into the language. I tried to read Tony Hoare's writings on his CSP but it soon runs into a whole calculus of CSP that is over my head.

Before that we did not know what it was called. We just did it. Our operating system had channels built in.

With both of these we could split code into processes and spread processes around processors with hardly a change in the source.

Somewhere I heard Allan Kay say that almost no commonly used OOP language is anything like the Object Oriented he had in mind. In his view messages are really messages, they may fail to be delivered. Objects were independent minded, they could choose not to reply. And so on.

That sounds just what I have in mind for my problem. I guess it is a pretty common or garden problem. Which looks like this:

With the added requirement that all of that can be replicated as many times as there are flows to handle in a single process.

Yeah, I really enjoy the actor model concept, though I haven't gotten to use it much in practice. I think it's the right amount of dynamic-ness without over-doing it.

So far Axiom is the nicest actor framework I've found, but it's not really maintained I don't think. I actually forked it to try and move it all over to using the smol async executor and drastically simplifying the code. I almost finished my fork, but turns out I never found the time to maintain my fork either. :wink:

There's new Rust actor frameworks been popping up quite a bit, but you can also just use plain 'ol channesl and such to implement the pattern yourself.

The Erlang language, which I've never used, apparently is built on the concept of actors and a lot of Rust actor frameworks are inspired by it.

Speaking of history, Objective-C uses messaging instead of virtual tables to do OOP -- I can't quickly find a good description, this old SO answer is the best I could come up with: https://stackoverflow.com/a/982356

I seem to remember reading that the runtime could even rewrite the caller to use unconditional call instruction. Amortised, this would make message passing faster than C++ vtables or manually written dynamic dispatch in C but I don't know if that's what was really happening or it was someone's experimental code or I'm mixing things up.

I have an aversion, for no particular logical reason, to anything called a "framework". Perhaps it's that gut feeling that I want my code to run the show, that is to say me, rather than some framework calling my code if it feels like it. Or it's the fact that one gets lost in a sea of alien terminology. Or it just feels like "bloat" for anything I want to do. Or a worry about how such a frame work will play with whatever other code I have. For example, if you start to use the Qt GUI toolkit you soon find your entire code base is dependent on Qt and infected with it unless you take care.

Which is probably why I have always changed the page whenever I come across talk of "actor model".

Except recently I have softened up on that stance, using Rocket for doing all my HTTP lifting. And React for that ghastly browser stuff.

Anyway, Alices' actor in Tokio concept, which I prefer to think of as CSP in Tokio, hits the spot nicely.

Reading that SO reply I'm not convinced Objective-C uses messaging.

Instances of a class have an isa pointer, which is a pointer to their class object. The selectors of methods in each object are stored in a "table" in the class object, and the objc_msgSend() function follows the isa pointer to the class object, to the find this table, and checks whether the method is in the table for the class. If it cannot find it, it looks for the method in the table of the class's superclass. If not found, it continues up the object tree, until it either finds the method or gets to the root object ( NSObject ). At this point, an exception is thrown.

That sounds like function calling and parameter passing to me. Where the method is found by searching various levels of tables up the class hierarchy.

Interestingly Javascript has done that since forever. Even before it had a "class" keyword.

Thing is message passing and method calling (which is really just function calling) are very different abstractions to my mind. Function calling is something going on in control flow in a program. Messaging is something that, at least conceptually, happens over wires or other media.

In the case of Transputer it really could be wires. Those channels one used in ones code could be between processes on a single chip, or they could map to actual hardware channels between chips. It was easy to build huge arrays of Transputers to distribute processing around.

Ever since I started with Rust I have pondered doing CSP with "plain 'ol channesl". I dismissed the idea as wasteful/inefficient as that would have been real threads. Great if the work the actors/processes do is long winded and compute intensive, not for simpler things.

But doing it with async tasks promises to be way more efficient for what I have in mind whilst being a neat way to organise code.

1 Like

Essentially your point is "use mpsc channels"?

Yes.

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.