I'm familiar with the Steele quote. The manner in which C++ programmers were "dragged halfway to Lisp" concerns primarily manual memory management and all the bugginess attending thereto (Java came out before the STL and smart pointers were widely adopted). Concerns about memory safety were a significant part of the impetus for developing Java in the first place. Steele was responding to complaints that he had turned his back on Lisp, by countering that he was instead bringing the C++ crowd closer to Lisp with a C++-like language that had Lisp-like memory management.
But the reason why Rust is such revolutionary computer science and, quite possibly, the most interesting thing to happen to PL design in decades is because with safe Rust you get all the memory-safety advantages of Java or Lisp, without a GC because the borrow checker statically guarantees object lifetimes. So Rust programmers don't need to be dragged halfway to Lisp the way C++ programmers were in the mid-90s, because Rust has the same memory-safety guarantees with none of the drawbacks of GC.
> with safe Rust you get all the memory-safety advantages of Java or Lisp, without a GC
Safe Rust does not protect against memory/resource leaks when reference cycles are present. To avoid those, tracing GC is still needed - and most likely unavoidable in the general case. Note that avoiding reference cycles is a global concern that can't be localized to any single part of the program, so trying to ensure this statically is roughly as hard as proving that a random piece of C/C++ code does not corrupt memory.
You can of course stick to tree-like allocation patterns where object lifecycles nest cleanly, and that's what the borrow checker is all about. You can also use arenas/regions, and future Rust versions will hopefully make those easier to use.
Java came out before the STL and smart pointers were widely adopted.
This is a strange comment.
Java 1.0 was released in Jan 1996. (Java wasn't very useful before 1.1) However, Stepanov proposed STL to ANSI/ISO committee in Nov 1993, and HP released a working version to the Internet in Aug 1994.
"[W]idely adopted" is an editorial term. It is meaningless without some backing evidence. ("Never been worse" has a similar sentiment.) How do determine what counts?
I worked on enterprise C++ for years in the mid-2000s that didn't use any smart pointers. There are many huge, old enterprise C++ projects that don't use STL or smart pointers. And, there are still many huge old Java enterprise projects that use shitty cast-from-Object-type to pass around typed data, instead of generics, or something better. Not much being said here!
the most interesting thing to happen to PL design in decades
"[D]ecades" is a wild overstatement. In the last 10 years, I would vote for LLVM, which greatly improved the velocity of (experimental) programming language development. Would Rust have developed so quickly without LLVM? Probably not. Look at the speed of development in Rust, Swift, Zig, and many others that use LLVM as their backend. It is night-and-day compared to 20 years ago in a GCC-only open source compiler world. I remember the bad old days where GCC was the elephant in the room, but so hard to add and maintain frontends, that few did it.
Rust has the same memory-safety guarantees with none of the drawbacks of GC
I never saw this before. Are there any counterpoints?
Rust spent its entire innovation budget on zero-cost memory safety! Basically, instead of you tracking lifetimes (like in C with malloc/free) or the runtime tracking lifetimes (like in a GC'd language), Rust lets the compiler do this with a 'borrowing' system.
Borrowing in Rust means objects exist in two states: owned (which get dropped when they fall out of scope, like in C++), and borrowed, which means some other scope owns the object and we only have a reference to it. The compiler verifies that a borrow cannot outlive the actual object, so it statically prevents use-after-free errors.
Rust also has a distinction between constant and mutable values, and statically checks that any mutable references are exclusive, and that immutable references are only shared with other immutable references. With this it helps prevent race conditions or other such mistakes.
Finally, Rust also actually has smart pointers in case you truly don't know when an object won't be needed anymore, although the names are a bit different than in C++; there's Rc/Arc for reference counting (like shared_ptr), Box for owning pointers (like unique_ptr?), and RefCell, that's like a runtime borrow checker.
Apart from these features that prevent use-after-free and aliasing, Rust also has a feature called 'unsafe' with which you can bypass all these and e.g. work with raw pointers. Unsafe is generally used sparingly (and if not, attracts a lot of criticism, like happened to actix-web), and the safe abstractions on top also provide more pedestrian safety features like bounds checking. You can skip bounds checking on e.g. a Vec, but doing so actually requires you to drop into unsafe yourself, since the get_unchecked function is marked unsafe in the stdlib.
Small interesting side note: I'm pretty sure Rust is actually doing very little new things, PL-design wise. It's more of a realization of theory that has been around for years if not decades.
But the reason why Rust is such revolutionary computer science and, quite possibly, the most interesting thing to happen to PL design in decades is because with safe Rust you get all the memory-safety advantages of Java or Lisp, without a GC because the borrow checker statically guarantees object lifetimes. So Rust programmers don't need to be dragged halfway to Lisp the way C++ programmers were in the mid-90s, because Rust has the same memory-safety guarantees with none of the drawbacks of GC.