Rust is the first language that has emerged in the past few years that solves enough of my problems that it would be worth not only learning & teaching an entirely new language, but also sacrificing the maturity of the language ecosystems I’ve become accustomed to.
I highly suggest you read the "Guide" provided by the language developers or this won't make much sense. These are just some of my thoughts and are intended to highlight particular things that stand out to me. I am just a practitioner and not an expert in any of these languages, so I have probably made some incorrect assumptions and out-of-date assertions. Bare with me.
Rust feels like the first time momentum has gained behind a true systems programming language that uses modern PL design techniques to prevent common errors when dealing with memory. It seems like others have previously either been too anemic to be worth adopting or too abstract to provide proper control. The type system and assignment semantics are designed specifically to prevent these errors, a bit more of a bookish approach, uncommon to the category.
First, immutability by default. Thank you! No dogmatic forced immutability, but a reasonable default that catches a nearly unbounded list of programming mistakes and encourages developers to embrace safer design. The pattern of tucking mutability away underneath well-tested abstractions is a proven one, and the language authors have adopted this as a core convention. They’ve also borrowed a great deal of other useful features & conventions from the functional programming community, including my favorite: Rust doesn’t have null pointers.
Safe pointers and their associated ownership semantics are brilliant. It took me thousands of hours of debugging crashes in C code to intuitively understand how to use pointers and their associated syntax correctly. The compiler disallows incorrect use of pointers. This means someone learning through trial-and-error can quickly have success, with the compiler as their guide. Explicitly declared "unsafe" sections allow sandboxing unsafe code, in contrast to the C++ world where it’s common to interact with unsafe code, but with no guardrails to bump up against.
Rust is minimalist. It has just enough sophistication to solve the problems in its domain. The authors admit that it is designed to really solve problems traditionally saved for C/C++ or that spill into Java for practical reasons. It takes an incredible amount of restraint and vision to enter the systems realm and not try to build something ultimately compromised-but-with-wider-appeal. It helps that Rust is being designed and implemented alongside a real project (Servo), a sort of symbiotic relationship that allows the language to iterate and develop quickly.
It's useful for writing code that can be included in a wide range of software through a binary interface. There's no walled garden that creates a xenophobic island of libraries and frameworks specific to the language, as typical with language ecosystems built around virtual machines. Functions can be exposed through the standard C ABI with compatible semantics. On the flip side, it has also dealt away with most of the pain when linking in other people's Rust code in binary form through the "crate" system. This is seriously painful in C++ and probably hinders its growth in many ways, making it difficult to share & reuse code. However, those looking for the runtime flexibility provided by JARs in the JVM will be disappointed. The compiler verifies all of the interaction upfront and yields optimized binaries, so the "crate" must be available (binary or source form I believe) at compile time. This feels to me like the right compromise.
Instead of an ornate type system, Rust has a relatively straightforward design that is supplemented with a safe macro language. Some languages such as Scala and Haskell achieve similar ends with extremely flexible syntax and advanced type systems. Like C/C++ and variants of Lisp, common logic that would be difficult or impossible to abstract using the type system can be encoded into macros for “syntactic abstraction." Rust's macros are functionally much closer to Lisp and are far less clunky than C. Macros are hygienic, which somewhat isolates the scope of the macro and prevents many common errors. They are also clean parts of the AST instead of a bolt-on, making them quite a bit more human readable than C preprocessor macros. I can definitely see “clever” developers overusing the macro system, but I’m hopeful that the fundamental language provides enough power to discourage it.
Enough abstraction is provided that it could be quite a productive application programming language. This remains to be seen, but short of the need to be concerned with the additional semantics of pass-by-value, I don't see much of a gap between it and Java 8 (the language) or C# in terms of tedium. The vast majority of application-style code would use stack allocation, which is straightforward, and not just for primitive types as in Java. Heap allocation is admittedly more cognitive work than it is with Java or .NET, but unlike C, the compiler automatically manages deallocation without additional runtime cost, so the extra work is minimized to just the bare essential "box" syntax with no real opportunity to make mistakes. C++ has some less intrusive forms of this (smart pointers), but they leave far more room for programmer error.
It remains to be seen how efficient the heap will be over the lifetime of of a complex Rust program that is heap-intensive. Fragmentation is a real problem in some C/C++ software. A nice side effect of Java's generational garbage collection is that the vast majority of objects are initially allocated into an eden space which is constantly being evacuated. Only long-lived objects have a chance to pollute and fragment the heap. The modern JVM also provides a production-ready compacting collector (G1), which can resolve the vast majority of real world fragmentation problems. Unlike environments with managed heaps, Rust uses real pointers that can't be safely relocated, so unless I’m misinformed, it’s not possible to compact the heap in a generic fashion. It could be that in most cases the additional memory necessary for high-throughput garbage collection negates the advantage of a compact heap.
At first I was a little disappointed to see that Rust had foregone a typical C++ style exception system. I have heard the reasons for such a decision before in other language communities. I expected to read deeper to find something like a clunky convention of passing errors via return values. First, Rust conventionally segments errors into crashes and non-crashes. As I understand them both, this is a similar strategy to Go, but with some subtle differences that make it less tedious. Non-crashing errors rely on a "Result" return type that allows handling these in a concise and type-safe manner. The type system catches common mistakes and the library provides macros to cover common logic when dealing with this class of errors.
Rust leans hard on its task system to handle crashing errors or "failures," which I think is fantastic. Tasks are the built-in lightweight concurrency primitive, so it feels natural to contain failures by sandboxing them by task. Instead of unwinding the stack to an exception handler, the task containing the failure crashes immediately. Parent tasks have the opportunity to watch for failures and execute a compensating action. The standard library even includes a macro that provides a similar feel to try/catch but spawns a task to execute a function and calls the error handling block during failure. Tasks are a first-class citizen of the runtime and feel extremely intuitive to use. Here again I feel like the designers have found the right balance.
So this is all very hopeful for the most part. In the coming weeks I’m hoping to spend more time digging in, learning more, and writing enough code to find the rough edges. Rust is obviously very early on in its life and probably unsuitable for whatever you are working on, but I am very hopeful for its future.