We're choosing Rust, and not Go, C++, or Node.js
TL;DR: We decided to go with 🦀 Rust (not Go or C++) for the Synergy 3 background service (currently written in Node.js), because we believe it will give our customers a better experience. Java wasn't considered.
We're not choosing Rust just because it's Stack Overflow’s most loved language for four years in a row, though most who made the leap to Rust have fallen in love and stayed. We're choosing Rust mainly for it's memory safety benefits. Incidentally, Rust is a bit more green than other languages, and the Rust community is the most welcoming to trans people (thinking about our hiring strategy). If you just want to laugh at funny stuff, scroll to the bottom of this article.
This article is a bit technical, so strap in...
The Synergy 3 service
Synergy 3 is made up of three parts:
- the C++ Core (which does the actual mouse and keyboard sharing),
- and, the Node.js service (a temporary prototype, written in TypeScript).
The service provides auto-discovery (though mDNS), keeps the configuration synchronized between all computers, and will provide other features in future such as 1-click auto update for all computers. I'll discuss the service more later on in this article.
Our journey of making the decision
After deciding that the service needs to be written in something with better performance than Node.js, we eventually settled on Rust and Go as possible options (excluding C++ early on based on our lessons from Synergy 2). The team took some time to develop a few small demo projects in both Rust and Go to get a feel for the languages.
Rust and Go are very different languages, so they're hard to compare. Nonetheless, we had a meeting with the dev team, and came up with a spreadsheet-matrix.
The formula and weighting are somewhat arbitrary, but essentially the score on each row is multiplied by the weight in a hidden column, which is then summed at the bottom. So, the further down the list, the less important an aspect is in terms of it being a deciding factor. The sum is then expressed as a fraction of 160 (which is basically a magic number). The point wasn't to create a brilliant equation to find the perfect choice, but simply to have some way of expressing what the out-of-five scores meant overall (which are arbitrary anyway). This will do; it's good enough.
Here's a few definitions of what we mean in our decision making table. A lot of the scoring is very subjective, team-specific, and project-specific. So, those numbers are maybe not useful for making decisions in other projects or with other teams, but it should give you some insight into how we made the decision for Synergy.
- Stability: How stable the Synergy 3 service is likely to be (i.e. memory safety, etc).
- Dev time: How much development process we're able to make in the time we have.
- Training/adoption: How easy the language is to pick up for various developers.
- Recruitment - Cost: How much it'll cost the business (job ads, admin, interviews, etc).
- Recruitment - Popularity: How many developers actually want to use that language.
- Recruitment - Availability: How many people are looking for roles in that language.
- Libraries: Are there libraries available for things we need and what are they like?
- Community: How strong, approachable, and helpful is the community?
- Language features: Do the language features work well for our needs?
Memory safety is the primary deciding factor
We uncovered that memory bugs are our biggest concern, since the Synergy 3 service is a long-running process. We also considered that in 2019, Microsoft uncovered that 70% of their bugs were memory-safety related.
For memory safety, we have a few options. Code written in Node.js and Go could be made memory-safe with enough work, but they're both GC based, which inevitably leads to engineers easily missing memory safety issues, unconsciously relying on the language runtime to clean up after them. C++ on the other hand has no GC, so you have to be more disciplined with memory management, but the language itself isn't going to protect you. You can't blame C++ for memory leaks, only yourself. For example, break encapsulation or mix C pointer arithmetic with C++ and bam, memory bugs... but it's easy done, especially with less experienced developers. How about unique_ptr? Certainly, but you don't have to use it.
Rust, on the other hand, simply doesn't let you be unsafe with memory (unless you leak memory on purpose, of course). This is a double edged sword, because of two reasons:
- It makes learning the language harder; the borrow and ownership concepts take a while to learn. For instance, Rust will be quite unforgiving if you call the a function twice with the same variable, as you'll get a compiler error. C++, Go and Node.js will let you do things like this without any immediate or obvious consequences.
- In Rust, you're forced to think about memory allocation and memory management. Yes, perhaps in some way C++ makes you think about these things too, but not by way of immediate compiler errors... instead, in C++, you often learn this at runtime. Matt Miller, a Microsoft Security Engineer, is known for speaking about the fact that most of Microsoft patches in the last decade were fixes for memory safety bugs, and that's possibly a contributing factor to the Hyper-V team considering using Rust.
Why not stick with Node.js?
The Synergy 2 service (where mistakes were made) was written purely in C++, so initially we were deciding between option 1) rewriting the Synergy 3 Node.js service in C++, and option 2) sticking with Node.js for the service.
Node.js is excellent for breaking ground and writing code quickly, so it was great at the prototype stage. That would have been a bit more of a chore in Rust, Go, or C++ since it was changing so much and we weren't totally sure if the code we were writing would be the right solution for our customers. But, we're quite certain about what we have now, so the whole development team feels that it's time to port the code to something that uses less memory and CPU.
The Synergy 3 service background process will be running on a user's desktop for months on end, quietly working away in the background. So, memory bugs result in a horrific user experience (and if the user isn't very technical, they have no idea why their computer has turned into a laggy mess). We actually saw similar memory safety issues in Synergy 2's C++ service, so it's a real problem that we have experienced.
For our product, using Rust instead of Node.js will result in a better user experience.
What does the service do?
The service's main function is to provide auto-discovery (we use mDNS for this) for networks that allow this, so you don't need to type IPs. Previously, we used Bonjour in Synergy 1, but took it out because it didn't work very well. You can still enter IPs manually if your network doesn't permit multicast, or if you have different subnets. The service also keeps the configuration synchronized between all of the computers in your setup (so you can change any setting from any computer, unlike Synergy 1). In future, we're planning on using the service to provide other features such as a 1-click update for all computers, so you can update all of your Synergy installations from a single computer (if you want to) instead of doing them individually.
It's easy to think that the background service might be just a simple state management process or just a watchdog for the Core. If only... as it turns out, in it's prototype form, it's currently a really expensive state management process in terms of resources because it is running on Node.js.
Big change... the background service in Synergy 3 now runs on every platform (Windows, macOS, and Linux), and now enables Synergy to work on the macOS login screen (I tested it the other day, and it works really well, if you don't use FileVault).
The new service in Synergy 3 is quite different to the Windows-only service in Synergy 1, which basically has one job; to launch the Core in the correct Windows session (something that Microsoft introduced in Windows Vista, for security). Actually, in Synergy 3 we still have this original background process, but we've called it the daemon. However, we'll be attempting to merge the Windows daemon into the new service so there's only one background process to worry about. Much of what the old daemon does (such as getting Windows session info from Win32) isn't possible in anything but C++, so we'll need to use foreign function interfaces (FFI), otherwise known as an API wrapper, regardless of if we use Rust, Go, or Node.js.
But you can't really compare Go with Rust
I spoke with around 20 of my most trusted CTO contacts. To keep it objective, each conversation started with something like: "know anything about Rust or Go?"
Here is one of those conversations. His response:
"[Go is] just another C-like language at the end of the day. Rust is another beast entirely. On the basis of anecdotal information from other people I've spoken to, most people like the idea of it but find it extremely difficult to work with. If you ask me I think it will be short-lived for that reason but it's anyone's guess."
I asked him if he thinks that Rust might be short lived because it's harder to work with than Go. To which he replied:
"Well, maybe just hard in general, rather than in comparison to Go explicitly. They are, to my understanding, really quite different in their target market: Rust is, I think, supposed to be a modern replacement for C++ - i.e. something very close to the bare metal; whereas Go is, I think, aimed at a higher level of application. So I think it's really about Rust vs. C++ rather than Rust vs. Go."
Go developers on the Discord Gophers server tend to agree with this point of view; Rust is not really comparable, as it has a totally different purpose.
Interestingly, Rust seems to have a reputation of being hard to work with. Most likely because of the borrow checker and ownership concept, which enforce memory safety. But, existing Rust developers of course have a different view.
"I find rust far easier than C++ to work with."
"The borrow checker specifically can be a pain, but overall, it's far easier."
"Rust trades short term ease of use for long term robustness."
Source: Rust Discord server
Another one of my CTO contacts had an interesting perspective on Rust vs Go. On the topic of choosing between the two...
"Definitely Rust, especially if you’re interested in shipping a binary that just works. Rust is a fantastic dev experience for near C++ type work. So maybe a wee less dev friendly than Node.js - but you can get your DevOps flow nearly identical. Plus you can ship a WASM, which may give you some creative opportunities for things like Chromebooks.
My experience with Go was cool, but really for shipping microservices into environments I control. I was only mildly into the language itself - it felt like it was sniffing in the right direction, but I kinda feel like Rust looked at C++, C#, Node.js, Python and Golang to get inspiration and took most of the best bits - for the language, compiling, targets, ecosystem, community, etc."
🌿 Going green with Rust
I don't really want the green aspect to be the focal point of this post. I added this section purely because I thought it was quite an interesting way to look at Rust. The topic of Rust being a green language seems to cause a bit of annoyance in the Rust community, with some having the view that: talking about energy saving or sustainability in programming is just a marketing trick or some kind of post-rationalization! 🧐
Incidentally, we recently migrated our PHP code to Node.js which uses TypeScript (so, more energy savings there by about 30%). Though, we're actually now considering rewriting some of that code in Rust which would not only result in lower energy use, but lower latency responses too (which would improve the customer experience).
AWS also wrote about sustainability with Rust. Even with the millions of Synergy users who will eventually benefit from Rust, Amazon's case is of course much more impactful than ours. Power consumption is an important topic to consider in data centers, since according to the article, "Worldwide, data centers consume about 200 terawatt hours per year. That’s roughly 1% of all energy consumed on our planet."
Some programmer humour to finish on
From the Reddit thread, "I haven't understood what rust is for", these quotes are said in jest, but I actually think that like many jokes, they're deeply rooted in truth. So, for the truths that they convey, it's definitely worth including them in this article.
If you're familiar with Rust, these jokes don't need explaining, and if you have to explain a joke, it's not very good. But, I will nonetheless add some commentary for those who are new to Rust.
"The original purpose behind Rust was to give people the ability to smugly talk about how unsafe C and C++ are, but it's recently been repurposed as a viable systems level language"
Most of the discussion about Rust tends to lean toward memory safety. And, while I haven't come across anyone in the Rust community actually being smug, I can see how this might happen... I mean, Rust is obviously better for memory safety (oops).
"Rust is a language where you try to convince the compiler that your code is right and end up losing most of the time"
Many developers who are new to Rust tend to struggle at first with ownership, the breakout feature of Rust which removes the need for GC; master ownership, and Rust will be your best friend for memory safety. If you ignore ownership, you'll constantly fight with the compiler.
Also, there's the borrow checker, does many things to ensure memory safety, such as ensuring that all variables are initialized before they are used. It also enforces that you can't move the same value twice, and you can't move a value while it is borrowed.
"Rust is a programming language made for Rust developers."
The uniqueness of the compiler really sets itself apart from any other language, and the learning curve is initially quite steep. So, when you start learning Rust, it can be tough, but once you've got the basics and you really lean into the concepts, the language becomes easy to use.
"Rust is used to calculate the correct band width of stripy pink and white thigh highs for devs."
Get your programming socks on Amazon. The Rust community attracts more trans programmers than any other programming community, because they receive less hate there than in other spaces. Anecdotally, other programming communities have never seen as many trans folk as the Rust community. The Rust community tries to shut down any bigotry and has been quite effective so far. Transphobia in the programming community in general is much more prevalent than it is in the Rust community.