A preliminary review of the Rust programming language

The Mozilla Foundation has been developing an exciting new programming language named Rust, that is designed to be a low-level language capable of matching the performance of C/C++, but with the safety of Java, the concurrency of Go, and many of the modern features of high-level languages like Erlang, Haskell, and OCaml. After reading the documentation and playing with bits of the language, I find myself struggling with some of the concepts of the language.

Ownership and borrowing in Rust are very complicated, and I can tell that it is going to take me a while to learn how to program with the straight jacket that they impose. With C/C++ you are free to screw yourself with memory leaks, buffer overflows, dangling references, data races, etc., but you get maximum control and speed. With Java, Go, and every other language with a garbage collector, you get poor run time performance, but you never have a memory leak or a buffer overflow, although you can still have the concurrency problem of data races. Objective-C and its successor Swift get around the performance hit of a garbage collector by using counted references to determine when to free up unused memory. Manual freeing of memory in C/C++ and using counted references in Objective-C and Swift will make a program run roughly 25% faster than a garbage collector, but the concurrency problems still remain with data races.

Rust solves all these possible problems, by only allowing one variable at a time to have write access to a piece of memory, so you can never have a data race. Once that variable goes out of scope, the memory is automatically freed. It sounds like a great idea, but it makes it damn hard in practice to do simple things like pass a variable to another function, because the current function no longer has ownership of that variable and can’t access it. Rust places a discipline on the programmer that lazy me resents but understands to be good for me, like eating my spinach and exercising every morning.

In Rust all variables are immutable by default (which means that they really shouldn’t be called variables any more, but constants). You have to explicitly declare variables to be mutable with the keyword mut if you want to be able to change them in the future. The syntax for defining variables is cumbersome, but at least declaring the data type in variables is optional if it can be understood from the value initially assigned to the variable:

let price = 23.99; //32 bit floating point immutable variable by default
let tax: f16 = 0.05; //16 bit floating point immutable variable
let quantity: u8 = 7; //8 bit unsigned integer immutable variable
let mut total: f64 = price * quantity; //64 bit floating point mutable variable
total = total + total * tax;

In Rust you even have to declare whether a reference to a variable is mutable or not and the compiler checks that there is only one mutable reference at a time.

Swift has a more succinct syntax to accomplish the same goal. It uses the keyword let to define immutable variables and var to declare mutable variables, so you don’t need to type two words to declare a mutable variable:

let price = 23.99 //immutable variable
var total = price * 7.0 //mutable variable
total = total + total * 0.05

In Swift you also don’t need to use ; (semicolon) to terminate a statement like in Rust, because Rust makes a distinction between a statement, which terminates in a semicolon and doesn’t return a value, and an expression doesn’t which terminate in a semicolon and returns a value. This distinction is confusing and doesn’t need to exist in my opinion. Rust could have also made let optional if the data type was being declared and placed the variable type before the variable name so the colon isn’t needed in variable declaration.

I think that the Rust language designers could have created a much cleaner syntax if they had let variables be declared in the following way:

let price = 23.99 //32 bit floating point immutable variable by default
let f16 tax = 0.05 //16 bit floating point immutable variable
u8 quantity = 7 //8 bit unsigned integer immutable variable, let is optional
var f64 total = price * quantity //64 bit floating point mutable variable
total = total + total * tax

The one thing that the Rust designers got right compared to to Swift is the use of short keywords in lowercase to designate the data types of numbers, so there is never a confusion about the number of bits. Swift follows the C/C++ convention of designing int to be the number of bits of the architecture, so the programmer is not sure whether it has 32 or 64 bits (without doing some manual checking). Rust avoids all this confusion by eliminating ambiguous keywords like int and float, whose number of bits can vary depending on the architecture. It is much clearer (and easier to remember) for the programmer to use i32 for 32 bit signed integers, u64 for 64 bit unsigned integers and f64 for 64 bit floating point numbers in Rust than to use int, Int32, UInt64, float and double in Swift.

The Rust compiler is a persnickety shrew that complains about everything that I do, but everyone tells me that using Rust will make me better programmer and eliminate all sorts of tricky bugs that you don’t normally spot when compiling C/C++. Mozilla estimates that 50% of the bugs found in the Firefox web browser could be eliminated if the program was written in Rust rather than C++. Given all the memory leaks, stalls and crashes that I find in Firefox, Rust will be a wonderful thing if it reduces the buggy nature of my favorite web browser.

Before Rust, I never once considered whether a string was allocated to the stack or the heap, but Rust forces me to think about it constantly. Rust has the immutable str stored in the stack and the mutable String stored in the heap and getting them to work together requires quite a bit of technical wizardry.

Trying to do simple things like string concatenation requires more typing by the programmer than a normal language, as shown by this example from the documentation:
let hello = "Hello ".to_string();
let world = "world!".to_string();
let hello_world = hello + &world;

It is beyond me why the Rust compiler isn’t smart enough to simply automatically convert a str value to String when assigning to a String variable. Even more annoying is the fact that the + operator isn’t designed to be able to concatenate a String with a String. It is only able to concatenate a String with a str, but according to some bizarre rule in Rust a &String (a reference to a String) is able to automatically convert to a &str, which can be concatenated.

In my opinion, the language designers should have made this possible:
let hello: String = "Hello ";
let world: String = "world!";
let hello_world = hello + world;

PHP is probably the least elegant language ever designed but after using Rust, I have gained a profound respect for that little . (dot operator) in PHP that makes it possible to concatenate anything as a string with minimum hassle.

I haven’t used the bizarre ownership and borrowing rules in Rust long enough to figure out whether all the pain they impose is worth it. Rust wasn’t designed for the trivial kinds of programs that I generally write, since performance is totally irrelevant for me and I never use concurrency. With a modern computer, I have more processing cycles and excess memory to waste than I will ever know what to do with. Even if I’m programming in PHP, which has the performance of a hobbling turtle, it does really matter to me. However, the whole point of programming for me is to explore new ideas and expand my mental horizons, so Rust fascinates me. There is no practical reason for me to use the language, but producing code that the Rust compiler will accept is a mental challenge which I can’t resist.

There is a constant tension in the language between wanting to be a low level language that gives the programmer ultimate control and the greatest performance and being a high level language like Python that insists that the programmer do things the right way that avoids potential errors, even if isn’t the fastest way. This tension is evident in the implementation of the for loop.

The Rust language doesn’t have a C-style for loop with an initializer, condition and updater. Instead, for loops are only designed for stepping through an interation, which is inefficient, but less likely to introduce errors in the program. This code in C executes much faster:
char ** colors = ["red", "green", "blue", "purple", "yellow", "orange", "brown”];
for (int i = 0; i < sizeof(colors); i++) {
printf("%s\n", colors[i]);
}

than this equivalent code in Rust:
let colors = [“red”, “green”, “blue”, “purple”, “yellow”, “orange”, “brown”];
for color in colors.iter() {
println!("{}", color);
}

Rust seems to have made it purposely difficult to use its for loop. Since Rust doesn’t allow C-style for loops, it should have designed its for loops to be able to automatically iterate through arrays, vectors, tuples and structs similar to the way that foreach works in PHP and for(x in y) works in JavaScript with arrays and objects, but Rust’s for loop only works with a special iteration known as a slice, so arrays have to be first converted into a slice by using the iter() method in order to used in for loops.

Of course, it is still possible in Rust to implement a C-style for loop by using a while loop, but it isn’t encouraged by the language designers:
let colors = ["red", "green", "blue", "purple", "yellow", "orange", "brown”];
let i = 0;
while i < colors.len() {
println!("{}", colors[i]);
i++;
}

Rust definitely wasn’t designed with simplicity in mind, but rather to avoid introducing errors in code. It has the keyword loop, which saves the trouble of having to type out while true {…}. The purpose of the loop keyword is not to make faster code (since a good compiler automatically recognizes that there is no need to evaluate a condition that is always true), but rather to remind me as a programmer that I am dealing with an infinite loop so I had better remember to add a break to stop the loop.

The same logic is a work in the way Rust forces me to add the keyword mut to every variable and reference that might change just to constantly remind me of its changeable nature. Another example of Rust forcing me to work harder just to constantly remind me of the potential risks is the way it forces me to manually document the different lifetimes of each reference. If references used in functions or structs have different scopes (i.e., might be freed from memory at different times), then the Rust compiler refuses to compile code unless I manually document those different lifetimes of the references. This does nothing to help the compiler per se, but rather it is designed to force me as the programmer to pay attention to the risk of the references disappearing.

At first I nearly blew my top when I read in the Rust documentation that the purpose of lifetimes is descriptive, not prescriptive, which means that it doesn’t actually change anything about the lifetime of the references. I asked in frustration, “why the hell am I forced to do this bizarre ritual just to get some stupid code to compile?” Nonetheless, after I reflected on all the potential bugs that lay in the different lifetimes of references, I began to see the logic behind the annoying ritual. Documenting lifetimes in the code is a blunt way for the compiler to force the programmer to pay attention to different lifetimes and make sure that she understands exactly when references will go out of scope in her code. Again, I’m not sure if I will ever need these kind of reminders in the kind of simple programming I do, but I can see how it would be useful when developing a complex program like a web browser, where the lifetime of references introduces all sorts of subtle bugs that the programmer never imagines when banging out the code. Rust forces her to stop and pay attention to the potential bugs inherent in different scopes for references.

One of the things that I heartily dislike about Python is the lack of the ternary operator which is found in C/C++, Java, PHP and JavaScript:
int x = y > 10 ? 1 : 0;
It is more cryptic to read, but so much quicker than typing out:
int x;
if (y > 10)
x = 1;
else
x = 0;

Rust allows something similar by using if … else as an expression:
let x = if y > 10 { 1 } else { 0 };

It takes a bit more typing in Rust than the traditional ternary operator found in C/C++, PHP and JavaScript, but it is also a bit more readable and less likely to introduce mistakes. I can live with this compromise between readability and terseness, whereas Python’s insistence that I not use it at all just annoys me. Python is frankly a much better designed language and certainly more performant than PHP, but I still use PHP because it gets in my way less often than Python and doesn’t try to dictate to me how I program. The fact that PHP is a more popular programming language than Python according to Github and Stackoverflow shows that many programmers agree with me.

At this point, I have not used Rust enough to decide whether I love or hate the straight jacket Rust imposes on me to avoid potential bugs in my code. Its nagging compiler is a big hassle, but the language has enough cool features and such a novel way of preventing bugs at compile time that I suspect that I will keep learning it. It certainly intrigues me enough to keep kicking the tires and give it a few more test drives, before I make up my mind about the language.

One of my frustrations with trying to learn Rust is the poor quality of the documentation. The official introduction to the language is available at https://doc.rust-lang.org/book/ and is known as the “The Book.” It is meant to be a quick introduction to the language, but is is often too quick, with little explanatory text and too few code examples to be useful. Frankly, you need to already know C/C++ in order for some of the sections of The Book to make sense. For example, in discussing strings and borrowing, it assumes that you already know how to reference with & and dereference with *. This lack of detail would be fine if The Book was filled with links to the full reference at https://doc.rust-lang.org/reference.html, so the reader could easily discover more, but it isn’t. Often the full reference isn’t very helpful either, again assuming that the reader knows C/C++ or another programming language which implements the feature. The full reference provides almost no code examples to explain the features, so the only recourse is to go to the Rust forum or IRC and ask someone. People are very helpful, but frankly, the documentation should be much better so that it is not necessary to ask for help.

To give just a brief example of this lack of detail, The Book in its section on comments makes no mention of any type of comments except single line comments that start with //. Reading that section, I assumed that Rust, like Python, must not allow block comments that allow code to be commented out in the middle of lines and in multiple lines. One of the reasons why I stopped programming in Python was its lack of block comments, which I find to be essential when trying to debug code. After reading the section on comments in The Book, I almost decided that Rust was not worth my time because I don’t want to waste my time dealing with any language that makes it hard to block out large sections of the code when debugging. I was ready to abandon Rust at that point, but then I discovered that /*…*/ was in the index of The Book, but it pointed back to the same section that made no mention of block comments. Then, I checked the full reference on comments and found this unhelpful text:

Comments in Rust code follow the general C++ style of line (//) and block (/* ... */) comment forms. Nested block comments are supported.

Line comments beginning with exactly three slashes (///), and block comments (/** ... */), are interpreted as a special syntax for doc attributes. That is, they are equivalent to writing #[doc="..."] around the body of the comment, i.e., /// Foo turns into #[doc="Foo"].

Line comments beginning with //! and block comments /*! ... */ are doc comments that apply to the parent of the comment, rather than the item that follows. That is, they are equivalent to writing #![doc="..."] around the body of the comment. //! comments are usually used to document modules that occupy a source file.

Non-doc comments are interpreted as a form of whitespace.

That’s it. There was not a single code example of how to use the comments or even a link to help people who don’t already know C++ style of comments or never seen a nested block comment before. The text on doc comments in the full reference is similarly cryptic, assuming that you know what “doc attributes” means and how to use them. The link to attributes doesn’t explain anything about comments. If the Rust writers don’t have the time to provide code examples and a clearer explanation, then they should at least link to C++ documentation with code examples which clearly explains how to use these features. I would have expected this chaotic state of the documentation before Rust was released for production use, but version 1.0 of the language was released on May 15, 2015, so there has been plenty of time to improve the official documentation if it were a priority. Another failing of the documentation is that there seems to be no way to download it, print it out as a PDF or read it when offline. The Book also doesn’t explain how to install Rust offline, which I found to be necessary since the downloader in the installation script kept failing due to my sporadic internet connection.

The upside to Rust is that it is community driven, and anyone can get involved with the project to help improve the documentation. The text for The Book is a github project, so the community is allowed to contribute to the text. Because Rust is community driven, it has an active community of people who are willing to answer questions and help you learn the language, but that also means that there is no 550 page PDF on the language like Apple publishes for Swift that clearly explains its features.

Rust clearly needs to organize its community to improve its documentation. One way of doing that would be to create a web site like PHP has which allows the community to comment on each feature in the language. I continue to use PHP despite its poor design partly because the language is better documented by its community than any other language I know. Almost every time I encounter a problem using a function in PHP, I find a comment in the documentation explaining how to get around that problem. The comments are culled and edited so they have a high signal to noise ratio and are worth reading. If The Book allowed comments on each section, I would definitely take the time to add a comment about block comments and nested block comments that the original author omitted and provide code examples of each type of comment to help the next reader who is not familiar with C++ style comments. However, I doubt that I will ever take the time to figure out how to contribute to Rest documentation. It takes too much time to figure out how to do it and gain acceptance into the system, and by the time I have enough experience with the language to be able to contribute, I probably will not longer notice where the documentation is hard for beginners to understand.

Rust is designed to solve a specific set of problems, but I question whether it will gain traction among programmers that do not face the sorts of challenges faced by Mozilla in developing a web browser which needs to worry about security, fast execution and concurrency. There are so many different ways to accomplish the same goal in programming, that that for general programming it mostly comes does to a matter of personal taste and preferences which language a programmer choses. This may sound strange considering the technical nature of programming, but there is rarely a right programming language. Even if you need a reasonably fast program, you probably won’t go wrong, no matter whether you choose C, C++, Objective C, C#, Go, Julia, Swift, Java, Rust or even Python with PyPy. I will probably never notice that half second difference between any program I write in PHP compared to C, and even if I do, it probably won’t matter with a couple more iterations of Moore’s Law.

Style and personal preferences matter a lot more to programmers than we like to admit. I never cared for the wordiness of Pascal, so I never bothered to learn Ruby. C was the first programming language I ever learned, so I have always favored languages ever since which use C-style syntax. I am a free software ideologue, so I refuse to touch Microsoft’s C# and have avoided Java simply because it was started by someone who publicly feuded with Richard Stallman over the use of code from a variant of Emacs. From everything I have read, Swift looks to be the perfect language for people who want the performance of C, the safety of Java, and a simpler syntax than Objective-C, but I won’t touch the language with a 10 foot pole because it is developed by Apple and I am deeply opposed to Apple’s policies regarding planned obsolescence (which have a sizable environmental impact), DRM (digital rights management) and suing rival companies over so-called “intellectual property.”

I am excited by Rust partially because it is developed by Mozilla, which is an organization that I deeply respect and support. I use Mozilla’s Firefox rather than Google’s Chromium, because it is developed by a non-profit whose primary mission is to promote a free and open internet, despite the fact that Chromium is considered better than Firefox in most technical respects, such as memory usage, speed, security and likelihood of crashing. The fact that Rust is developed by an organization which I trust and it employs an open development model which I admire is just as important to me as the language’s syntax and its technical characteristics.

Many people love Rust because of the high quality of the feedback which comes from the compiler. Part of the reason why the compiler can provide such detailed error messages is the fact that it is based on LLVM, rather than GCC. It might seem strange that I would feel more loyalty toward GCC than LLVM, but GCC was started by Richard Stallman and is the most visible flagship of GNU project and the free software movement. As a passionate advocate for free software and the struggle for digital freedom, I fear that GCC will loose out to LLVM. GCC uses the GPL 3 license, which prohibits it from ever being privatized or incorporated into a proprietary program. The development of LLVM started at the University of Illinois, but it has been mostly financed by Apple, because the company resented the restrictions of the GPL license on its use of the GCC compiler.

GCC used to create faster executables than Clang with LLVM, but today executables from Clang/LLVM are as fast as GCC in many areas, but it compiles faster, is more modular and provides more debug information, so there is no longer a technical reason to keep using GCC. At this point, almost all the Linux world continues to use GCC rather than Clang/LLVM mostly out of familiarity with the tool and inertia. I fear that Linus Torvalds who has never liked the GPL 3 will one day switch to using Clang/LLVM and many of the Linux distributions will follow suit, especially the ones which are less concerned with the struggle for digital freedom. At this point, Red Hat is fully committed to GCC and employs a number of the key developers of GCC, and Debian is probably too committed to the fight for digital freedom to abandon GCC in favor of Clang/LLVM, but many of the distributions would likely switch compilers if the Linux kernel abandons GCC. The loss of GNU’s flagship project will erode one more brick in the already shaky edifice of the free software movement.

By using Rust, I wonder if I’m contributing in some small way to the gathering movement toward LLVM and away from GCC. I wouldn’t worry if I thought the struggle for digital freedom was winning, but in many ways I see it loosing. In a technical sense, free/open source software (FOSS) has won, since it now runs most of the cell phones, tablets and servers in the world. Most mobile devices today run on FOSS, using either Android with a Linux kernel or Darwin with a BSD-derived kernel in iOS and OS X. Most servers now run on Linux and the infrastructure of the internet is almost entirely FOSS. Yet, as computing has switched from desktop PCs to the cloud and mobile devices, user freedom is in greater jeopardy than ever, since so much of the software and the data is locked up in the server farms of companies like Google, Facebook, Apple, Microsoft, Dropbox, Amazon, ebay, Yahoo!/Verizon, Twitter, etc. and so many of the programs running on mobile devices are designed to spy on users and collect their data.

For GCC to loose to Clang/LLVM would be a blow to the ideological fight for digital freedom, since GCC convinced so many developers of the practical utility of free software and attracted many to the GNU project in the early days of the project. Linus Torvalds was convinced to use the GPL 2 for the Linux kernel because he admired GCC and saw it as a model to follow. However, replacing the compiler with the GPL 3 license for one with a BSD-style license will have little practical effect on digital freedom for most users. In fact both GCC and Clang/LLVM can be used to produce proprietary software and spyware used to surveil our activities. I understand the technical reasons why LLVM is better than GCC for the Rust compiler, but it still leaves a sour taste in my mouth to use LLVM, knowing that Apple helped create it in order to remove a GPL program from Apple products.

Nonetheless, with Go, Swift and Rust all vying to become the next successor to C/C++, I would prefer Rust to win, because I support the mission of the organization behind it, whereas I fear the goals of Apple and Google, whose business plans often run counter to the digital freedom of users. I also fear that Mozilla is fading into irrelevance as the browser market share of Firefox has now dropped to only 11.8% on the desktop and 0.6% on mobile devices. Rust becoming a rising star among programming languages could help revitalize the stature of the Mozilla Foundation in the tech world and give the organization more influence to fight for a free and open internet.

Although I hope Rust will become one of the dominant programming languages, I suspect that it will never reach that level of popularity among programmers. The use of Rust will undoubtedly continue to grow, but I suspect that its persnickety rules on ownership, borrowing and lifetimes will make it too difficult to learn and will require too much dedication on the part of the programmer for it to ever become a general language to replace C/C++. It will become one of the favorite languages for mission critical programs that need performance, concurrency and safety, but outside that niche I can’t see it gaining much traction. Likewise, I predict that Swift will also be confined to the niche of the Apple ecosystem. It will swiftly replace Objective-C as the language of choice for Mac OS X and iOS, but I doubt that it will gain many adherents among people programming for Android, Windows, Linux or the web. It appears to be too tied to Apple’s ecosystem and too heavily dominated by Apple for it to ever become the general replacement for C/C++.

It is very hard to predict the future popularity of programming languages. I doubt that anyone 20 years ago would have predicted that JavaScript would become the most commonly used programming language in the world, and that PHP would be third on the list. Likewise, few would have guessed in the early 70s that C born in the research lab at Bell would become the dominant systems language in the world.

The language with the best prospects to become the next C/C++ is Go, but it doesn’t have enough of the high level features to make its use compelling over C/C++ and its garbage collector imposes too much of a performance penalty for many applications. I think that Swift has the right idea about how to deal with memory problems by using counted references to free up unused memory and how to make a language simple enough to use, but performant enough to be useful for systems programming. In order for a new compiled language to become the next successor to C/C++, it needs to have these features of Swift, but be open enough in its governance to attract more than one ecosystem of programmers and add more of the high-level language features like Rust to make it worth the effort of migrating from C/C++. Until such a language appears, I predict that C/C++ will continue to fragment into communities using Go, Rust, Swift and Java with no clear winner among them.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s