There’s been an awful lot of talk about Rust lately, and I’m not talking about that reddish crap that eats away at your car bumper. I’m talking about the programming language that’s been voted “most loved” by Stack Overflow for the past three years. In an age where programming languages come and go, it is incredibly difficult to determine what is simply “the new hotness”, vs what is going to stick around for a while. I mean, for Pete’s sake, I have counted almost 700 programming languages in existence. Why in the world would anyone pay any attention to this new kid on the block.
There’s been a bit of a push, lately, in compiled languages (e.g., languages that compile into native code, such as C++). Compiled languages generate machine code that runs directly on the CPU. The result is that a program that runs faster and has access to lower-level OS and hardware features, but usually at the expense of readability, maintainability, portability, and/or security. There have been two languages developed recently that stand out as contenders to bring native compilation to applications in a safe and secure manner without having to sacrifice those ‘ilities you had to in the past: Rust and Go.
I decided to dive a little bit deeper into Rust to see what the hubbub was all about.
Rust Is A Functional Programming Language
Don’t let it fool you. If you buy into Rust because it offers security, efficiency, and speed, that’s not all you’re going to get. You’ll also be investing in one of the most impressive functional programming languages that I’ve seen. The idea of functional programming has been around for a long time, but if you’ve never weighed the benefits and consequences of functional programming over other methodologies, you are missing out.
What exactly does Rust do to lend itself so nicely to functional programming?
- For starters, everything is immutable by default. Side-effect free functions are paramount to functional programming.
- The Option type is built into the language. This, in and of itself, is enough to squash the billion-dollar mistake. Granted, if you don’t know how to correctly use the Option type, your code will be littered with hundreds of little unwrap() statements. Yes, you should be very ashamed.
- The Result type is also built into the language, which is a game-changer when it comes to exception handling. Through type safety, you are forced to handle both success and failure conditions, thereby eliminating a large number of bad assumptions.
- Tuples, structuring, and destructuring are built into Rust. This allows you to transfer information between methods without having to create a large number of superfluous, intermediate data types.
- Rust supports lambda expressions (e.g., closures), which can be used to construct higher-order functions.
- The Rust type system separates interface and implementation with trait and impl, respectively. However, you can implement any trait on any type. There is power in this, as it lets you add methods to types for which you have no control, similar to extension methods in C# or monkey patching in Ruby. Again, this is huge for functional programming.
- The pattern matching supported by Rust is powerful and concise.
There Are Niceties for Everyone
There are a number of features that are icing on the cake for most programmers.
- The compile-time type inference makes defining variables much easier. To me, it feels a bit like writing type-safe native code with TypeScript.
- There are no pre- or post- increment/decrement operators. Say goodbye to ++ and –. I’m not even sorry if this offends you. If I could tell you the horror stories I’ve had to endure from these operators.
- The language is surprisingly terse. I’m not usually a fan of abbreviations in either code or programming languages, but the Rust team has made everything so consistent that the abbreviations are natural and actually make sense.
And Yet, There Are a Few Things I’m Weird-ed Out About
It never ceases to amaze me. Every time I get my hands on a new technology, I’ll plow through the documentation and everything makes sense. Well, until it doesn’t. I had several double-take moments that forced me to ask the question, “Why the blankety blank did they do it that way?”
- Shadowing looks to be a freaking nightmare. And why on earth can you redefine a variable in the same scope? Anyone remember those hoisting nightmares from JavaScript? You know, the reason they built the ‘let’ keyword into the language…
- For newbies, the range operators are begging for off-by-one errors. I understand that the exclusive operators are very useful when using for loops, as you can simply provide the length as the upper bound, but this is still begging for typos. Part of my problem was that the syntax was in flux until version 1.26, but the fact that the exclusive range operator takes less typing is going to prove to be confusing for some victim…er developer.
- The lack of a return statement for a function is awkward. Simply omitting a semicolon makes it a return statement? This is just asking for trouble.
And There Are Things I’m Still Not Sure About
For the most part, I do like what I see in the language, but there are still a few things that I’m on the fence about.
- The ideas of ownership and borrowing play heavily into alleviating a huge number of common concurrency issues. Rust’s solution is not as succinct as C#’s, JavaScripts, or Go’s. To be honest, I need to see more real-world examples to see which one is better. It may prove well for the Rust team to add some syntactic sugar to the language so that we wouldn’t have to think in terms of channels, senders, and receivers.
- I’ve never really been a fan of documentation in comments. I know that it is a huge gain for developers that are consuming third-party components. But for developing in-house applications, this documentation quickly becomes burdensome and stale. With that said, Rust’s built-in support for documentation offers some advantages to prevent comments from becoming stale. First, the comments use markdown format, which greatly simplifies the documentation. Secondly, the examples that are included must be valid code that is compiled by the Rust compiler. This forces the examples to be kept up to date. Still, whether they are or not will be determined in the future.
What do you think of Rust? Have you had success in implementing enterprise applications with it?
Nice post!
On shadowing: I thought so at first and I still agree somewhat, but: shadowing lets you ensure that a variable is not used again later, which is great in some cases.
On the return statement: If you look at expressions vs. statements in Rust (which are part of the reason for the lack of ++ and –), it makes more sense. A function is just like an expression that can be evaluated. The explicit types in function signatures ensure that there can be no errors here.
Thanks for your feedback, Rafael!
Yes, after thinking about it a bit more, I can definitely see the value in shadowing. And, with the primary building block in Rust being the function, I can even see it as a necessity. I will say that I would much rather see a shadowed variable than a non-shadowed variable that is sprinkled throughout the code. At least the changes to that shadowed variable are isolated to that scope. Bad developers don’t need shadowing to write crappy code, and I can see how a good developer can harness it successfully.
Thanks for the information regarding the return statement. I’m always a fan of being explicit rather than implicit, but it is nice to know that Rust isn’t going to let me doing anything atrocious here. This language is so powerful and terse it is quite easy to forget that we’re still getting static type checking.
>The lack of a return statement for a function is awkward. Simply omitting a semicolon makes it a return statement? This is just asking for trouble.
There are return statements if rust if you implied they didn’t exist. It’s a standard idiom in haskell and lisp, and frankly, it fits well with `{}` blocks that “return” something to the outer scope.
>For newbies, the range operators are begging for off-by-one errors.
I’ve never seen this be a problem really, python has `range(0, 3)` where 3 is excluded, and it’s a relatively standard thing that’s shared among languages. It’s really not a problem.
>Shadowing looks to be a freaking nightmare…
Kind-of, shadowing is extremely nice where you explicate that something is truly no longer accessible (lifetimes do this too). For instance:
let x = something;
let x = f(something, x);
let x = final_calculation(x, y, z);
Just using it (`x`) as a simple inline scratch space is nice, and since there’s no dynamic scoping as in JS, nor can outer values alias, you do not have a problem with this.
> The ideas of ownership and borrowing play heavily into alleviating a huge number of common concurrency issues.
It’s not specifically concurrency issues which are solved. This is actually widely useful when designing data structures and interacting with them. Without a borrowing and ownership mechanic, you can easily find yourself attempting to access a memory address to a value that was moved, especially in a language which does not rely upon a runtime garbage collector.
> Rust’s solution is not as succinct as C#’s, JavaScripts, or Go’s
None of these have solutions. A data race across threads in Go, for example, can cause a SIGSEGV, which thereby crashes the entire program. This is something that Rust can catch at a compile-time, and which other languages require runtime race detectors. It may very well be impossible to prevent these errors without having a system exactly as Rust’s. Garbage-collected languages which guarantee thread safety do so by avoiding mutability entirely, thereby making all data structures immovable, immutable memory.
> It may prove well for the Rust team to add some syntactic sugar to the language so that we wouldn’t have to think in terms of channels, senders, and receivers.
What syntax would you propose? These are already high level primitives, and it’s important to know what they are, how they’re used, and what problems they solve.