This is a guest post by Bitbucket user Mohit Agrawal as part of Bitbucket’s writing program.
Author bio: Mohit Agrawal is a software developer from Indore, India and works at Oviyum Technologies, an outsourced product development firm. He is passionate about coding and new technologies. Connect with him on LinkedIn.
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.
The first thing 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?
- Will it run on the targeted platform(s)?
These should be the primary questions. But there are other things that might influence your decision such as:
- 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 would it be to solve new problems that you might stumble upon 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
We are creating a device to consume data from various sensors and provide real time analytics & intelligent assistance through web and mobile applications. We needed a language that was fast enough to allow minimum real-time latency and use limited resources of a SoC device.
Here is how we evaluated programming languages for our project.
Performance
Comparing the cross-language performance of real applications is tricky. We usually don't have the same expertise in multiple languages and performance is influenced by the algorithms and data structures that the programmer chooses to use. But as the benchmarks below 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.
Language | Time (sec) | Memory (mb) |
C++ Gcc | 1.94 | 1.0 |
Rust | 2.16 | 4.8 |
Java | 4.03 | 513.8 |
LuaJIT | 12.61 | 1.0 |
Lua 5.1 | 182.74 | 1.0 |
Python | 314.79 | 4.9 |
Concurrency
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, which means that having multithreading support was important.
Lua does not have native multithreading support. Though there are 3rd party workarounds, the performance and reliability of those are questionable. Rust, on the other hand, has built-in support for multi-threading and its ownership and 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 you more mental flexibility, as you can think more in terms of transformations, operations, and algorithms. Flexibility lets you move faster, change things quickly, and iterate at a faster velocity. But it comes at 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 the other hand, in a static typed language, a large number of errors are caught early stage in the development process, and static typing usually results in compiled code that executes faster 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 you about undefined behavior.
The right part of the chart below 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. This makes Rust difficult and more time consuming to learn than 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 particularly in Rust, you are explicitly forced by the compiler to write optimum memory-safe code.
Despite the fact that you're doing things like manual memory management that you do in C++, Rust has a lot of features and conveniences that almost make it feel like a high-level language. And Rust has a lot of abstractions that don't make it 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.
Rust also facilitates writing tests within the program itself through its powerful libraries like cargo test. Combined with Bitbucket Pipelines it becomes very easy to setup continuous integration & ship code with confidence.
Conclusion
Based on the above, Rust was a good choice for our project. 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 ideal candidate for such use cases.
However, interpreter based languages may be better when you don't have resource constraints and when high concurrency is not needed or is achieved using other mechanisms such as event loops.
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 the other hand is comparatively easier to approach, has a sizable community, does not come with decades of technical debt, and yet provides comparative performance. Memory safety & easier concurrency are just added benefits.
Love sharing your technical expertise? Learn more about the Bitbucket writing program.