Choosing a programming language(s) for a new product is an important strategic decision. It influences a lot of things and has long-term implications for hiring, culture and even the viability of a product.
But the first things to be considered is whether the language is viable for the particular problem statement you are trying to solve. Important questions are:
- How suitable is the language for your particular use case?
- Will it perform up to the mark?
- Will it run on the targeted platform(s)?
These should be the primary questions. But there are more things that might influence your decision. Like:
- How choosing a particular language will influence your turnaround time from idea to reality?
- What are the cost benefits of using a particular language?
- How easy will be it to solve new problems that you might stumble along the way?
Keeping these questions in mind, this article will try to explain our reasoning behind choosing Rust for our new product.
# Use Case
Our problem statement was to use an edge device that can process data from different sources in real-time and create a knowledge graph, therefore the language we choose must be fast to allow minimum real-time latency and use limited resources of an SoC device.
Comparing the cross-language performance of real applications is tricky. We usually don’t have the same expertise in multiple languages and performance is much more influenced by the algorithms and data structures the programmer choose to use. But as the benchmarks above show, it is generally believed that Rust performs on par with C++. And performs much better than other interpreter or JIT based languages such as Lua or Python.
As described in the use case above, we wanted to process data in real-time from multiple sensors. Our target platform, SoC devices, use ARM-based CPUs and generally have 4+ cores. We wanted to utilize all CPU cores, that means having multithreading support was important.
Lua does not have native multithreading support, there are 3rd party workarounds but the performance and reliability of those are questionable. Rust, on the other hand, has built-in support for multi-threading and Its ownership, borrowing rules help us write very safe concurrent code.
# Memory Safety
Dynamically typed languages give you a lot of flexibility. Type changes do not need manual propagation through your program. It also gives more mental flexibility, as you can think more in terms of transformations, operations, and algorithms. Flexibility lets you move faster, change things quickly, iterate at a faster velocity. But it comes with a cost. It’s a lot easier to miss potential problems and these problems are generally very hard to debug. Plus these features generally comes with a performance penalty.
On another hand in a statically typed language, a large number of errors are caught in the early stage of the development process, and static typing usually results in compiled code that executes more quickly because when the compiler knows the exact data types that are in use, it can produce optimized machine code. Static types also serve as documentation.
Rust goes above and beyond these points. Rust’s very strict and pedantic compiler checks each and every variable you use and every memory address you reference. It avoids possible data race conditions and informs about undefined behavior.
The right part of the chart above shows concurrency and memory safety issues. These are the most complex and unpredictable classes of errors and are fundamentally impossible to get in the safe subset of Rust. Moreover, all these type related bugs are dangerous and result in a variety of security vulnerabilities.
Type safety is one of the Rust’s biggest selling point and is the reason Rust topped as most loved language for 3 consecutive years in StackOverflow Surveys.
The way Rust achieved this feat is by using the concept of ownership of a variable. In Rust, every value has an “owning scope,” and passing or returning a value means transferring ownership to a new scope. You lend out the access to the functions you call, that’s called “borrowing”. Rust ensures that these leases do not outlive the object being borrowed. This not only makes it very type safe but also helps you tackle concurrency head-on because memory safety and concurrency bugs often come down to code accessing data when it shouldn’t.
# Developer Experience
Rust has a steep learning curve. Most of it is due to the “ownership” & “borrowing” concepts we discussed above. Which makes Rust difficult and more time consuming than garbage collected languages like Lua or Python. It requires one to be very aware of basic computing principles regarding memory allocation and concurrency, and It requires you to keep these principles in mind while implementing, this should be the case for any language, but in Rust particularly, you are explicitly forced by compiler to write optimum memory-safe code.
Yet Rust has a lot of features and conveniences that almost make it feel like a high-level language despite the fact that you’re doing things like manual memory management that you do in C++. And Rust has a lot of abstractions that make it not feel like manual memory management anymore.
Low-level control and high-level safety promises developers far more control over performance without having to take on the burden of learning C/C++, or assume the risks of getting it wrong.
When you want to implement a high-performance concurrent system with low resource footprint, the choice of programming languages is limited. Interpreter based languages tend to perform poorly in high concurrency & low resource environments. System programming languages are the idle candidate for such use cases.
C/C++ is the holy grail of systems programming languages. But there’s a reason C & C++ are also one to most dreaded languages in StackOverflow Surveys. For newer programmer coming out of other higher level languages, approaching C/C++ is hard. The learning curve is very steep. There are approximately 74 different build systems and a patchwork framework of 28 different package managers, most of which only support a certain platform/environment and are useless outside of it. After 30+ years of evolution, new programmers have too much thrown at them.
Rust on other have is comparatively easier to approach, has a sizable community, does not come with decades of technical dept, yet provides comparative performance. Memory safety & easier concurrency are just added benefits.