Async Rust and Node SerialPort
This year I took some time around the holidays to do some learning. Like last year I dove into rust but I didn’t go into it cold this time. I was gifted a “live book” called Rust In Motion which has audio, slides and text all mixed together. It was fun and covered the basics in a very approachable way. So I had that base which I had been building up over a few weeks, and my SerialPort co-maintainer Nick Hehr sent me a text about NAPI.rs. I don’t know if he meant to nerd snipe me but it sent me down a rabbit hole that took the rest of my week building a rust bindings prototype for node Serial Port.
a side by side comparison of `@serialport/list` and my rust bindings prototype
First off I had no idea what I was doing, I’ve never explored or used async in rust. I understand N-API for nodejs but haven’t used it much. But I have a use case, Node SerialPort has been stagnant and the bugs are mostly due to needing to use N-API and stay away from the ever changing ever v8 api. I have a whole host of electron users who deserve better. Additionally I think performance could be better and I think allowing for the use of workers, and maintaining our own thread pool would go a long way in that department. So can rust help?
Of course it can help! After a bunch of work I was able to release rust serialport binding prototype working. It does a non blocking cross platform Binding.list() that lists available serial ports. And it’s the smallest shippable surface area I could approach. It’s a pretty fundamental shift from the JS and C++ I’ve been using, so each layer of this prototype was an education.
At the top layer my prototype uses NAPI.rs for the NodeJS N-API bindings, build tools, and npm publishing! It puts all three together in a great package. It also loads a Tokio runtime if you want it, and I do. I found (and reported) some minor bugs (quickly fixed!), but it’s a great library and I currently recommend it. I think it just needs more time to mature and some more users to shake out the rough bits. While most of the docs are fabulous and very educational about binary modules in JavaScript work, I did find some areas a bit confusing. The maintainers however were very responsive and a few PRs later and I can proudly say I helped improve some of the recipes.
Tokio is my rust async runtime. It manages a thread pool for both blocking and non blocking tasks. Has ways to do async io, streams, channels, and has a whole ecosystem on top of it. It feels to me like what NodeJS is for JavaScript. It’s tutorial which I cannot recommend enough, brings you through making a redis client with a library and then slowly replacing parts of it with your own code. I found it a great way to learn.
The serialport crate is a blocking library for serialport access. It works! It’s a little hard to distribute as it requires some c++ libraries for serialport listing but I think I can overcome that. The blocking nature however leaves it not entirely usable on it’s own. There’s a wrapper library called tokio-serial which writes some async reading and writing but it’s a bit out of date and is actually a wrapper of a mio-serialport which is by the same author and is a lower level async serialport library. mio-serialport looks pretty familiar to node SerialPort’s c++ code for both windows and unix and frankly that’s fantastic. I feel like I need to fork and maintain some of these projects for our uses, but I’m not thrilled to fracture the ecosystem or take on the maintenance burden.
I have some notes about different ways this project could go. There is still a lot of exploration and experimentation to do but this was an exciting deep dive. Looking forward to more!
-Francis