Generic messages for inter-threads communication

I am working on my first Zig based process

  • multithreaded
  • modular
  • every module runs on own thread
  • modules communicate with eatch other using messages

Message reqs:

  • relative generic - understandable for the infrastructure
  • contain module specific information
  • ability to add new kinds of messages

e.g. HttpRequest does not meet the requirements, but it may be the specific part of the message

Example of simplest message that satisfies these reqs - Windows MSG “translated” to Zig:

pub const Msg = struct {
	// The message identifier. 
	// Applications can only use the low word; 
	// the high word is reserved by the system.
	msg: u32,		

	// Additional information about the message. 
	// The exact meaning depends on the value of the 'msg' member
	wParam: usize,

	// Additional information about the message. 
	// The exact meaning depends on the value of the 'msg' member
	lParam: ?*anyopaque,
};

What is Zig way to create such messages?

1 Like

I think the best way to create messages like this would be to use a tagged union. Tagged unions work similar under the hood, but will give you more type safety compared to the C-style approach.

6 Likes

tagged union will be OK for common messages (e.g. WM_QUIT :grinning:)

But let’s suppose following design:

  • module is pluggable
  • it placed in shared library and loaded in run-time
  • modules are non-related and developed by different developers/teams

I am not talking about theorethical situation, it’s usual practice

And usage of Big Brother tagged union does not look as good idea.

I think sometimes we need to use void* sorry *anyopaque, cast and pray

If you don’t know ahead of time what kind of messages there are, then yeah you cannot use tagged unions.

But I don’t really know why you wouldn’t know them upfront. If you have two modules that want send messages to each other, then surely they must be equipped with the knowledge of how to interpret these messages. So why not bake this knowledge tagged union? You could also make one tagged union for each channel or something like that, if you can’t maintain a global tagged union with all message types.

But of course you can always go back to the C way of doing things with anyopaque pointer and an integer (at least use an enum maybe?) tag, if you think that that’s the better solution.

Also a quick reminder: if you use different shared libraries, then you should always use extern structs/unions to ensure that both sides have the same memory layout.

5 Likes

forewarned is forearmed
thanks a lot

It’s possible to combine these approaches:

pub const MessageKind = enum {
     ok,
     more,
     greeting,
     // ...
     generic,
};

const Message = union(MessageKind) {
    ok,
    more: usize,
    greeting: []const u8,
    // etc..
    generic: struct {
        id: u32,
        payload: *anyopaque,
    },
};

But only do this if you can’t solve the problem without it. It’s not hard to add something extensible like this to a tagged union, but that doesn’t mean it’s a good idea to just throw one of those at the end of the union “just in case” or to make it “extensible”. It should represent a use you can’t satisfy in another way, like a plugin system where a consumer you didn’t write needs to use the message architecture.

2 Likes