My Tryst with Rust

For the last couple of months I have been working on a side project in Rust, the new language from Mozilla. It has been an interesting experience but ultimately I have decided to migrate to another language for the project. In this post I'll explain what I've learnt about Rust in the last few months and why in the end I have decided to go another way.



I have spent a lot of time working with scripting languages and languages without much of a compile step over the last few years and a few months ago I began to get a real hankering to work with a language again where the compiler catches a lot of the simpler errors before you even run anything. Rust is certainly a language that does that!



To give you a brief overview of Rust it is a cross platform language built to optimise for safety and speed of execution. It is a strongly typed language built heavily around interfaces and structs. To some extent it is similar to the old windows Component Object Model (COM) where an object declares the interfaces it implements and consumers operate on those interfaces and structs are used for passing complex data objects. Interfaces in Rust are called traits and when you define the parameters for a method you will typically define them in terms of either specific traits the method is expecting of its parameter or in terms of which specific struct the method is expecting. Methods are declared as applying to a certain struct or trait for a struct, standalone functions unassociated with any struct can also be defined.



Rusts memory management is where it really starts to separate itself from other languages. It uses neither garbage collection or explicit memory allocation and deallocation but a different concept, managed at compile time, called object lifetimes. The idea of object lifetimes is that by looking at when variables come in and out of scope combined with hints given by the programmer the compiler can work out for itself when memory needs to be allocated or deallocated and insert the appropriate code. In this way the language is extremely safe as null pointer exceptions and other memory related bugs are turned into compilation errors but still very fast as the compiler is able to optimise memory allocation in advance and there is no garbage collector to slow things down.



Another thing Rust does well is concurrency management. As well as keeping track of when variables memory needs to be allocated or deallocated the compiler also keeps track of where variables are being used and where they can be changed. When you declare a variable in rust by default it is immutable and its value can only be set at the time it is declared, to be able to change a variable you must explicitly declare it mutable within the scope. When a variable is used in a different scope it is borrowed by that scope and cannot be used in another scope until it is returned. This helps a lot with both concurrency and general safety. In general it ensures that objects (especially references) are not changed or destroyed by a function call unless you are expecting they could be. When it comes to concurrency and multi threading it means that thread safety is built in at compile time, most data race conditions cannot happens as only one thread can ever own mutable data at once.



There are lots of other exciting things about rust, such as the way it handles tuples and tuple assignment , the match statement which guarantees all branches of a conditional have been considered, and the way you can chain iterators. Memory management and variable ownership are the big ones though.



All this sounds great (and is really useful) but it comes at a cost. It is really hard to get code to compile in rust, especially if you are dealing with strings a lot. Because dealing with string involves allocating and deallocating memory Rust distinguishes between Strings, which are similar to a C string (an array of chars) with associated manipulated methods and String slice (&str) that are reference to part or all of a string. The fun begins when you start building new strings from slices of old strings and trying to tell the compiler how long the references to the different bits an pieces are supposed to stay alive for. This goes for other objects too. I spent literally hours fighting with the compiler trying to get it to compile what seemed like not terribly complicated code. Code that in C I would have just allocated the memory at the start and deallocated when finished, or in C# I would simply have used a StringBuilder and let the garbage collector handle any strings that fell by the wayside, became an hours long fight with the compiler spread over several days coding sessions.



Now if this had just happened once or twice I would have pushed through and kept going. But when it happens time and again you start to wonder if its worth all the trouble. Now to be fair when I got truly stuck and asked questions on Stack Overflow the response were prompt, friendly and helpful. The Rust community is really helpful and I am grateful for the help I received. The language is also fairly well documented. However I have taught myself many languages in the past such as FORTRAN, C, C# and JavaScript (Node.js) to name a few, none have I found so difficult as Rust. In the end I reached the point where it simply felt things were taking too long and switched the project to C#.



I think Rust is worth using if you really need its features; safety coupled with speed, or you have complex parallelism to deal with. However if you are writing an application where the odd null reference exception isn't going to break the bank and the multi-threading is relatively simple the costs of using Rust outweigh the benefits in my opinion.



I am interested in hearing other perspectives and opinions on this so if you have an opinion or would like to share your own experience please leave a comment below.



