i think rust just makes me suffer by forcing paradigms that just dont work with my specialization and the type of programs i write. cant believe ive been writing rust for 3 years now
@angelthorns I tried to write an IRC daemon in Rust. There is so much global state, that it is a painful task to actually do so. You have to basically lock a bunch of effectively global state, gather all the data, perform modifications to the data, queue everything you want to send, release the lock, then send the messages. You have to do it this way because you can't hold the lock across an await boundary (which, to be fair... is the right thing to enforce, because that's asking for a deadlock).
@Elizafox yeah anything that requires global state becomes awful
@angelthorns Yeah, there's arguments to avoid global state, but there are times you cannot avoid it. And when you need to mutate any global or effectively global state (passing state around in a structure you pass everywhere is global state in a trenchcoat), it becomes a nightmare.

@angelthorns Basically the pattern I came up with is:

1) Create Vec that will store client mpsc channels (which are thankfully Clone) and messages you want to send them
2) Lock state structure (I used an RwLock because for the most common case, PRIVMSG and NOTICE, you don't need to mutate much client state)
3) Perform CRUD-style updates to the users
4) Enqueue messages to send to the users as well as their queues
5) Drop the lock
6) Process all the messages in the Vec you just created

It is... actually much more messy and heavy than the same C/C++ code, and not a super scalable design. But the architecture of a chat system makes it virtually unavoidable unless you store thousands of per-user locks... which is not great overhead either and requires locking/unlocking potentially thousands of locks per operation too.

I mean, hey, if anyone else has better ideas I'm open to it, but this seems to be the way everyone suggests doing it.

@angelthorns I guess an alternative could be to use Rust's stock mpsc queues which don't require .await but that is just as heavy weight and might block if the queue is full.

Also, a major footgun I found. Never use tokio's RwLock if you can avoid it. Sure, it's Send. But then you can cause a deadlock without even realising it, if you keep the lock held in a coroutine, then .await... if something else tries to use that same lock. BOOM. Deadlock.

@[email protected] @angelthorns yes, a loop with a select, spawning tasks for accepted connections, putting the handles into a JoinSet. A similar pattern will suffice for the channels

If you used Golang channels it will feel familiar

In Rust it can also be augmented with Stream for "async iterators"

It's actually kinda rare to need locks in application code in Rust
@[email protected] @angelthorns It will look more like a state machine than systems code, but that's kinda the point of using async

@natty @angelthorns Yeah, the problem is IRC's state machine is enormous. Clients alone have 20+ pieces of state (nick, ident/username, host, real name, mpsc queue, modeset, target change, token bucket, last ping received, pending ping, away message if any, channel membership list (needed for WHOIS), MONITOR list, capabilities list, quit flag and message (if the user is leaving, you need to broadcast it and send an ERROR to the client before disconnecting them)... things like nick, ident/username, host, mpsc queue, modeset, capabilities, etc. all affect how a message is set, what kind of message is sent (with or without tags, etc.), what is sent with it (nick/user/host is included in most messages from a user), etc.

Much of this state doesn't change often, but other state is pretty changeable (like nickname or away message or channel membership), sometimes by the user, sometimes by other users (think /SANICK etc.) so it is extremely painful to deal with it.

@natty @angelthorns This doesn't even begin to touch on channel state, which is pretty similar although less than what is on clients.
@[email protected] @angelthorns Doesn't sound too bad if you know when to use what tools

I'd probably go for a more event loop based architecture but I've never touched IRC so hard to tell
@natty @angelthorns IRC traditionally used an event loop for this reason yes.

@natty @angelthorns You do in this case because mutating client state is very common in IRC.

Nickname changes, membership changes, rate limiting, target change throttling, user and channel modes, checking ban/quiet lists then exceptions... channels are effectively global, so they are everywhere.

@natty @angelthorns Most IRC commands touch on some kind of state in some form. The main exceptions being PRIVMSG/NOTICE, those are basically read-only operations minus token bucket limiting/target change update, but that is very brief.

You can mitigate it with things like caching things like if a user can send a message and only invalidating the cache if the mode set or ban/quiet/except list is updated... but then you're doing cache management, and you still gotta manage locks because those lists can be updated by another coroutine.

IRC is... well, a very antiquated design. This is just how the protocol works.