Vorner's random stuff

Directly to production after 6 hours of coding

This one is probably more of a personal story about Rust than the rest of the articles here. And it is a half-in-joke (as opposed to the usual half-serious).

Sometimes, I write about the things that frustrate me about Rust. While I can ramble about it for some time and with due passion, it’s mostly a positive thing ‒ I’m frustrated by whatever language or tool I use. I wish things were better. I question the sanity of its authors (especially if the author of the tool is me). But this makes the improvements possible. And I write about the frustration in Rust because I care. I’ve given up on many other languages and tools as lost causes.

Anyway, today I want to share a positive story. It’s not about 1000× speedup of something (I’ve done that already with the matrices), or saving millions to a company (I hope to do that one day). It’s about a very small and mundane thing, but I want to illustrate why it is I care about Rust.

The background: the minimalistic music player

When I work, I like to listen to music. The old-style way, by having a collection of songs on my disk, so it works even when I’m offline. When writing a software that should run on a router and testing things out, this happens a lot. When I travel, this happens a lot. I still have to find a hotel where the WiFi works for 10 minutes without disconnecting. I don’t really need fancy playlists or collections sorted by the genre and the ability to rate the music. I simply throw the whole set at the player, tell it to shuffle and then press the Next button on the keyboard whenever I don’t like the song.

Over the time, I’ve tried many full-featured players. WinAmp back at the times when I still had Windows on my computer. Something with a name starting with x that imitated WinAmp when I switched to my first Linux (at the times when Fedora was still called Red Hat). One player that came bundled with Kde 3. The other player that came bundled with Kde 3. The yet another player that came bundled with Kde 4. I tried mpd and gave up in the middle of the tutorial, telling me how to set up my music database and let it scan the songs and how to move the songs from the database to a playlist (I guess this was more of a documentation issues, someone told me mpd was in fact easy to use some years later). This all felt too bloated and heavy-weight. I don’t need no music database, I just want to play songs. And I don’t want to give the player 1GB of RAM just to sit there (even now one of the machines I use is a cheap netbook with only 4GBs, it’s enough Firefox takes 2.5 of that for itself).

So eventually I wrote a program that got fed with a list of songs and listened on a unix socket so I could bind a shell command that sends next\n into it to a global shortcut in the window manager. The program used mpg321 (or maybe it was aplay at the time, now it is mpv) to actually play. The first version was written in Perl if I remember correctly.

And, over the years, every time I was learning a new programming language, or when the old version didn’t feel just right, I rewrote it in the new language or using some new trick. I could find a C version with threads, highly optimised C version that mmapped the file with a list of songs into the memory and pre-indexed that so it saved RAM, Haskell version, Python version, another Perl version I was using until recently… Well, I could find it if I really wanted to go looking, but some of it might be still on some DVD backups in my parents’ house. In short, it became a kind of learning-something-new ritual.

Except for Rust. For some reason, I was using Rust for almost 2 years now, considering it the go-to language for most of my needs (at least when nobody else had any say in the decision) without rewriting the player in it. Until this Sunday.

So, how did it go?

I spend about 6 hours writing it. I tried using the 2018 edition for that and it felt good (the biggest change for me is probably that I don’t have to write extern crate any more). I even used the much discussed match ergonomic thing on purpose at some place and knew it at the time.

I used my own corona for the asynchronous stuff, to test it some more ‒ eating own dog food and such. And also because this is the kind of thing the library is for anyway: nobody cares about performance that much, with about 5 events per second and 2 simultaneous connections, but it is more convenient that bare-bones tokio or bare-bones juggling with threads and mutexes, and besides, application that does close to nothing doesn’t deserve more than 1 thread. Using more than one thread without a performance need is not elegant.

I cursed a little when I discovered that I have to use either a thread local storage or a mutex in a single-threaded application to store some mutable globally-accessible object.

I cursed some more when I discovered the standard library or tokio don’t even consider the existence of unix named pipes, the mechanism the previous version used to talk to mpv (by talk to I mean „pause“ and „quit“). A named pipe is like an ordinary unix pipe, but it sits on the file system, so two independent programs can connect to it (repeatedly). It needs to be created and then opened like a file. But it acts like a pipe, so it can return EAGAIN (unlike files, which claim to be ready to be read and written all the time even if they aren’t and then they block). And there doesn’t seem to be any obvious way to open a file and then plug it into tokio and tell it „treat it somewhat like a socket“. Thinking about it, tokio probably has only unix domain sockets, but not unidirectional pipes at all. I mean, I could put something together with all these AsRawFd traits and Evented, but:

I had to spend about an hour and a half, debugging a lockup, blaming it on Rust not knowing what to do with pipes and playing with strace only to discover that even opening the write end of the pipe blocks until there’s the other side ready to read it and that the Perl version launched the mpv first and only after that opened the pipe (I guess I know why now ‒ I have the feeling I might have spent several hours on that when writing the Perl version back then too ‒ reminder to use the great invention of code comments sometimes). And then I spent another 10 minutes switching to unnamed unix domain socket pair and screwing that one onto the file descriptor 4 of the mpv-to-be to be done with the damn pipes.

So, what’s the big deal?

If you read the above, you might think that I could have written it with the same amount of time and cursing in any other language I know. And it’s probably true. Well, maybe not make and bash might be a challenge too (hmm, thinking about that… 😇).

However, in every previous version, there was something, some nudging feeling, that something in the code is a bit off. Every previous version was glitchy for the first two weeks of using and I had to fix it several times during that period. The previous Perl version had a bug I never managed to find ‒ sometimes, the program just terminated. No crash dump, no error message, it just gave up and I don’t know why. Every previous version felt like a temporary solution until the next attempt.

No previous version ever had the prev/next navigation entirely correct, I always had some scenario of going forward and backward through the history where a song got played twice in a row or skipped. One of the previous versions sometimes started to play two songs at once for no apparent reason. Having to express myself in Rust’s Options and encoding the state in the forgiving-nothing type system made me think about it long enough to do it right this time, in a way Perl’s „everything quacks like a string“ attitude never would (or C’s „… like an int“).

The Rust version went straight „to production“. From the Sunday evening, I discovered no glitch and the thing feels finished, at piece.

I mean, what program ever feels finished? And I’ll probably do some thing with it eventually (I’m already thinking about keeping the list of song files in memory as a compressed inverse radix tree to save some of the RAM ‒ not that I would have any need of that), but I wouldn’t have to. Whenever comes the time someone makes a better language than Rust and I move to it, I’ll have a hard time finding the reason to rewrite it in that language. The bye-I-give-up bug was bothering me for ages, but eventually it was the excuse for the rewrite.

And this feeling ‒ the feeling that I think the program is correct, despite plenty of experience telling me no program is ever correct, is the reason why I care about Rust and why I like to use it. That I can finish the program, try it once or twice, put it into production and forget about it. Yes, it is longer than the Perl version. It probably took more effort to write. But hey, it’s worth it (and besides, it isn’t that much more effort).

Wouldn’t it be great if we were able to write programs the admins have to touch only when the hardware the programs run on goes to the silicon heaven and they need to install them onto new servers? Rust gives me hope this dream might one day be possible.

The result

You know, this is not about the destination, it’s about the journey. Or mostly. At least for me.

Alright, if you insist, the code lives in the github repo. Go find that bug and tell me it is not correct, if you have to.

It doesn’t contain the shell commands to control it, though. I was too lazy to take them out of my system and import them I thought it would be a nice exercise for the reader to reverse-engineer them, they are quite simple.

Anyway, it might be less work to spend the time writing your own instead of trying to integrate this into your own work flow and window manager.