1) Memory safety. If you live in the world of kernels and embedded code, your options are mostly C, C++, and (as of just the last few years) Rust. Of those, only Rust reliably prevents memory corruption mistakes like use-after-free.
2) Being a 21st century language. Rust has a lot of features that any new language would be expected to have these days: a unified build system, a library ecosystem that the build sytem knows about, slices over arrays and vectors, UTF-8 strings, convenient ways to de/serialize JSON, something like async/await, etc. This makes it competitive with languages like Java and Python for doing "everyday stuff", where realistically ~no one would use C. The main downside of Rust in a Java/Python sort of context is that the learning curve is a lot steeper.
A couple other notes:
- Some of nice 21st century features (like JSON) go away when you're writing embedded/kernel code, but others still work. Embedded ("no_std") Rust still has enums, error handling, slices, iterators, and sometimes even async/await. Those quality-of-life features are hard to replicate in C, even if you believe your code is 100% bug free :)
- If you know you're going to be writing multithreaded code, Rust's thread safety features are really unmatched. A lot of the same machinery that gets used for memory safety also turns out to be useful for thread safety. Libraries like Rayon make it surprisingly convenient to write multithreaded code, while also catching a huge percentage of mistakes at compile time. (Deadlocks are still possible though.)
Another very naive question. Are memory safety concerns from a cybersecurity standpoint or from less-dev error standpoint?
The dev-error is broader language guarantees, which dovetail into memory safety but are useful on their own e.g. data race safety, rich type system, ...
Cybersecurity issues make Rust a good choice for writing parsers for example. File format parsers handle scary data from the internet (JPEG, X.509, etc), and those often contain addresses and offsets that turn into raw pointers in the implementation, so they're a common source of security bugs. At the same time, they're usually isolated behind a reasonably tight API, so it can be practical to rewrite them in Rust even when all the calling code is still C or C++ or even Python.
On the other hand, fewer-mistakes-sucking-up-dev-time is an especially big deal in multithreaded code. There was a story from Mozilla about how they'd tried to add threading to part of Firefox (I think it might've been the CSS engine?) and failed multiple times, but then they succeeded in Rust. Threading bugs tend to be painful to reproduce, and Rust's compile-time checks are extremely valuable there.
These memory-related exploits disappear with Rust aside from in "unsafe" blocks (possibly the worst named keyword in any language... it should have been called "trusted"), and that means you have a smaller and more easily auditable attack surface for these types of memory-related exploits. Some code (e.g., FFI) can't be verified as memory-safe by the Rust compiler at compilation time so "unsafe" is there as an escape hatch. I've written a bunch of Rust since 2014 building things like webservers, realtime futures processing algorithms, MEV bots, etc., and I've only had to use "unsafe" a few times.
I've also worked in security on products at Fortune 100 companies, and C is a constant nightmare for CVEs. I think I have PTSD from having to update libcurl. The more software we have written in memory safe languages, the better off we all are.
It’s probably worth nothing that that vast majority of, and of large scale attacks, is basic shit that has nothing to do with memory safety.
There’s benefit to memory safety, but just cause you’re wearing a helmet doesn’t mean you’re completely safe.
Additionally, it’s not Google overall, but chromium project that find this, as well as not Microsoft overall, but specifically Windows. These are two projects that will naturally see a much higher rate of memory safety issues.
This isn't the case: https://msrc.microsoft.com/blog/2019/07/a-proactive-approach...
> Since 2004, the Microsoft Security Response Centre (MSRC) has triaged every reported Microsoft security vulnerability.
They've never qualified it as Windows only, to my knowledge.
1. Rust has a more modern sense of computer primitives than C does. For example, C has the char-short-int-long-long long set of types which correspond to 8, 16, 32, and 64 bit integers in practice (and yes, 5 types for 4 sizes), whereas Rust has u8/u16/u32/u64. Rust also has a builtin slice type, which is a desperately needed notion of pointer + size of array, and C's lack of such type has proven to be the source of much malware. The standard library is also somewhat richer too, you have better support for things like threads or networking than C/C++ does. But note that Rust is still like C in that it has a very thin runtime layer, and can also go a step further and have "no" standard library at all, which is necessary in several contexts.
2. The borrow checker. Effectively, use-after-free (or any other lifetime issue) vulnerabilities become compiler errors, and even many kinds of data races are also turned into compiler errors. Also it can do things like make iterator invalidation issues a compiler error as well.
1. A type system that's deeply inspired by OCaml, a language that's designed and used by a lot of programming language research people and is widely praised (just look at how every other language — C#, Java, Kotlin — is suddenly trying to pile half baked imitations of ML features like ADTs and deep pattern matching onto their existing OOP type systems), except they've both imemented the type system correctly and fully, and also filed off all the warts and awkward features, and replaced OCaml's verbose and underpowered first class module system with Haskell style type classes
2. got amazing, highly flexible and generalized iterator and monad support (you can use iterator methods on monads), basically second only to Haskell itself, that compiles down to the equivalent of hand written assembly code loops
3. an error handling system using monads that neatly sidesteps the issues with both traditional C style error handling and exceptions that combines with point two in an exponential curve of awesomeness for error handling
4. hygienic (from Racket) and procedural (eg Common Lisp) macros
5. And finally ofc their statuc memory safety and race condition safety, which is based on linear types ala Idris.
But most importantly, Rust's designers seem to IMO have carefully picked only the advanced programming language features that tend to make your code clearer as well as more correct, and help you model problems at the right level of abstraction to be intuitive, and also only features that can be implemented in a way that is as performant as the equivalent C++ code, roughly speaking at least. They also seem to have been very careful with how they integrate and implement everything in order to uphold those goals.
So you get to have 90% of the power of a language like Haskell, in a language that performs like, and has the low level capabilities of, C++, with the extra benefit of — for instance — less dogmatic puritanism and more deterministic and comprehensible performance and execution properties then Haskell, and less Lovecraftian horror full of surprise gotchas than C++.
It's kind of like the ultimate pragmatic language for someone like me who has gone deep into pure functional languages and other esoteric languages and wants to have as much as of that goodness as is practical.
A data race goes like this: At least two simultaneous execution contexts (maybe threads for example) are looking at the same object X and at least one of them changes it, but there is no particular order of these events agreed between these contexts.
In your head, even with parallel computing everything seems to happen in some sort of global order. A happens before B, or B happens before A. This is called Sequential Consistency, and it's a lie, the machine doesn't actually work like that, but humans can't really understand non-trivial software without this lie, so, all our high level software (and when I say "High level" I mean like the C Programming Language) pretends sequential consistency is always preserved and goes to some lengths to achieve that.
In Rust you can go about your business. (Safe) Rust promises this is true and it'll make damn sure. But in many languages like C or C++, actually you can very easily inadvertently construct a data race, revealing that it was a lie and if you do so all bets are off. Since you probably can't reason about your program's behaviour anyway, they figure "fuck it" and that's Undefined Behaviour.
Bonus round for languages which do better than most: Go says if you race a trivial object like an integer, you lose Sequential Consistency but this isn't automatically UB. Complex races are UB.
Java says races are never UB, but they do lose Sequential Consistency. Your Java Program is now very, very difficult to understand, but it's not nonsense.
OCaml goes furthest, it says your race isn't UB and it offers very tight constraints on what's wrong. OCaml's work on this is relatively new, so it may be a while before we're confident whether this is a manageable situation normal humans (well OCaml programmers) can handle.
Yes, thank you for pointing that out, ive had a bad headache for most of the day and knew I was saying the wrong term when I wrote it, but couldn't for the life of me remember what the right one was! And yeah I'm pretty familiar with this, although I wasn't aware of OCaml's strides
As I understand it, Rust lacks do-notation and higher-kinded types, making it hard to have true monads in Rust.
Scala would be the language with monad support comparable to Haskell.
Yes, it is very much true that rust cannot yet represent monads as type classes directly, since it does not have any form of higher kinded types yet — which is the reason why I said second only to Haskell. If it could represent monads directly, than I would have placed it equal to Haskell — as I would have placed scala!
But I do think it is important to highlight that it does have pervasive useful monad use in the language, and an interface that allows you to use a very large common set of powerful transformations on them via the iterator trait, and it uses the monads that exist in the language and the ability to use that trait on any monad a lot to increase the power of the language in a way no other language besides ones with superior monad support does that I know of. So I would argue that while it is still second to Haskell, it is still far above almost any other language not equal to Haskell in this respect. Which is what I meant by saying second only to haskell!
Also, HKTs are something that the rust team seems to be very actively looking to rectify with an in progress implementation currently available in nightly, so that won't be true for very long.
I believe that a monad is basically any container that has both a constructor and a flat_map function (and there may be one other requirement that I've forgotten). The special ability that Haskell has is the ability to write code that is generic over any such container.
`map: forall a b . (a -> b) => (m a -> m b)` and `pure: forall a . a -> m a`, plus some (mostly very natural) compatibility conditions between the three: `flatMap(pure)` does nothing, `map` takes the identity to the identity, etc.
It's definitely verbose, but underpowered? Modules are just as powerful as Haskell's typeclasses (and therefore more powerful than Rust's).
In my opinion they are, because any group of types and functions that implements a module signature must be wrapped up into a module that satisfies that signature at the point of use of any value that needs to satisfy that module signature, instead of a type just satisfying the given signature if the correct functions exist to manipulate it, which means in my opinion there's a lot less flexibility and it works more like constructing a new value of a certain type then it does having constrained parametric polymorphism in the way Haskell does. I think this is why although they technically can be used to imitate rust traits or Haskell type classes and allow a function to polymorphically take any type of that satisfies a certain interface, it isn't in practice used like that almost at all, whereas it absolutely is in Rust and Haskell. Witness for instance how both rust and Haskell have a generic type class for iteration that allows you to take anything that is iterable and use the whole module of functions available for iterables on it, so for instance in Rust you can use many of the same methods on a vector and an array and error checking monads, whereas in ocaml, even though something similar is in principle possible, it's almost never done that I've seen, because you have to essentially declare that I type implements a signature and how it does on every point of use where you want to pass a value of that type into a function that requires something with a certain signature. Instead functions are duplicated between modules for different types, like lists and arrays. Maybe that isn't underpowered per se, but it's certainly a practical consideration in the use of first class modules that mitigates a lot of what they are theoretically capable of that seems in my opinion to fall directly out of the conceptual model of using first class modules to do constrained parametric polymorphism, since values then must become modules.
As for the ability to represent the signature of monads — yes both ocaml and Haskell can currently do that while rust cannot, but there is actually currently active work on an RFC that would rectify that. So I suppose overall you might say that rust's type system is currently less powerful then the other two, but I really don't think that will be true over the long run, and it certainly isn't true of necessity, and rust has a type system with a great many benefits in practical power over OCaml's otherwise, in ways I feel impact day to day use more.
Second only in monad support to Haskell and equivalently powerful languages e.g. OCaml and Scala, but much better than mainstream languages, I should've said.
Also, I should have said that Rust helps with data races not races in general.
Also, I should've said Rust's trait system was more practically powerful / usable that OCaml's first class modules and potentially more powerful overall but not quite there yet.
This is what comes with writing enthusiastically, while suffering from a headache and brain fog. I swear I'm not an idiot and know my pure functional languages!
How does that differ from RAII?
I think i misunderstand you or lack knowledge, because this sounds exactly like RAII.
I know that rust has major compile time checks, but saying that the difference is that it reason about life time as difference to c++ is misleading. I think the major point of c++ compared to c is that c++ "reason about object lifetime statically" with deconstructors and RAII. And saying that rust do this and implying c++ don't is misleading.
One thing C++ programmers might be interested to learn, is that this doesn't only apply to simple variables and references; it also applies to library data structures and their methods. The Rust compiler doesn't really "know" what the .clear() method on a Vec does, but it knows enough to prevent you from calling that method while you're holding references to the Vec's elements.
It’s also a big part of C and C++’s learning curves, it’s just that the compiler doesn’t tell you about it; you’re being taught by segfaults and silent memory corruption instead.
There is one major difference. In Rust you can memcpy objects you own to a different base address and they will still work, unless they're pointed at by a Pin<> type (as such, code that requires objects to stay put in memory must take a Pin<> reference. This arrangement may potentially be replaced by a special "?Move" trait in future editions of Rust). C++ pins all objects by default and allows objects to have custom "move" constructors, to sort of enable them to be moved elsewhere.
In C++, objects that get "moved" must leave behind something that can be destructed cleanly; Rust has no such limitation, at least wrt. ordinary moves. Arguably the closest thing it has to a C++ move is the .take() pattern, which leaves a default-constructed object behind. But this is rarely used.
The general tradeoff is that the Rust pattern memcpy's compound objects more often, but has less gratuitous use of the heap and less pointer chasing compared to idiomatic C/C++. This is a valid choice once one has the compiler support that Rust provides.
An other major safety difference is that Rust uses destructive moves, once a binding is moved-from it becomes invalid / inaccessible (and it won't be destroyed).
In C++, a moved-from object is in a "valid but unspecified state", so it must be destructible (because destructors always run) but any interaction with the object other than destruction may be UB, and the compiler won't tell you (in the same way it won't tell you about borrowing issues whether it's UAF, invalidation, ...).
It seems that some Rust folks are still on Mozilla payroll.
Mozilla is still a member of the foundation, so they do support it in that sense, but they are one of many companies that do so.
Advanced typing
Build tools