I don't think this is a fact for _all_ GC'd languages. For example, Nim is a GC'd language and it's also multi-threading safe. It perhaps doesn't give as much freedom as Rust, but it does still achieve safety. The compiler restricts sharing of memory because each thread gets its own GC, and for locks the compiler verifies that variables are locked [1].
No, but Rust provides this memory safety without GC, which is AFAIK, unique in a mainstream language.
The type system knowing about ownership and borrowing also allows the elimination of race conditions (outside of `unsafe`, of course...). I don't know of a GC language that also does that, though obviously that doesn't mean there isn't one... ;)
Rust doesn't get rid of race conditions. It lets you write memory safe concurrent code. These are two separate things. You still have to be cognizant that code may not run in the order you expect.
Data races shouldn't be happening with a GC'd language with a proper memory model, getting this right is important. Haskell has no dataraces if you don't use IORefs in safe code. Afaik java has a happens-before memory model that can be used to detect races.
More generally, it lets you tie resources to syntactic scopes.
An example in the standard library is MutexGuard - you get one when you lock a mutex, and when you drop it, that releases the mutex. You can't forget to release the mutex, and you also can't release it too early, because then you lose access to the data protected by the mutex. There are ways to defeat this, of course, but you have to go out of your way to do it.
I've used a library which uses a similar pattern for writing to a ring-buffered message queue: when you grab a slot in the queue to write to, you get a SlotGuard, and when you drop it, you lose access to the slot, but that commits the slot and lets other threads read it.
I see this statement is oft repeated on HN, but no proof is offered in its support.
Rust can guarantee the elimination of race conditions in low-level multi-threaded code, but aside from that there's nothing really safer about its type system compared to say Java. And one would anyways use a higher-level concurrency library in Java, or perhaps the concurrency annotations, making the formerly mentioned advantage barely relevant.
Memory safety is certainly not the only kind of safety, but it is alas the only kind of safety supported by Rust (and every other language except C and C++).
> Memory safety is certainly not the only kind of safety, but it is alas the only kind of safety supported by Rust (and every other language except C and C++).
I think the parent poster's entire point was that Rust does provide safety beyond memory safety. Perhaps not as a forced concept, ie: you do not have to express all structures as finite state machines, but you can very elegantly express your structures as finite state machines. And you can do so in a way that most other languages can't, because of affine types.
I don't see why such a minor thing is considered an important safety improvement.
Using optionals, result types and match would give me a nice feeling too, but I won't claim that my code's safer because of that. Those are super low-level tools.
I realized it was an important safety improvement when I wrote a medium sized project in Rust. Put minimal effort into (manually) testing it (no automated tests), and then sat back watched while it ran flawlessly for a week, and then a month, and then 2 years. Zero issues.
I've never had that experience in any other language. Usually unless you're careful and put effort into testing and making sure that you handle every possible exceptional case, you'll have several bugs and crashes. Of course these are possible in Rust, but in practice I'vr found they're mucb rarer. Most of these issues are caught by the compiler.
I didn't attempt to qualify "importance" of the method, only give one example where Rust provides a safety mechanism that other languages do not; the ability to encode finite state machines into the type system with no risk of reusing invalid states.
Whether you care about that or think it's valuable does not change that it is a way of encoding a model into your type system to make a class of errors impossible.
Quantifying the impact of the method is very relevant though, because if using FSMs makes code 0,001% safer, your statements that this makes a class of errors impossible or that Rust provides additional safety mechanisms are both true, and yet should have no bearing on the real-world safety of a program written in Rust.
And the lack of evidence in this area suggests to me that the safety benefit is indeed negligible.
> I see this statement is oft repeated on HN, but no proof is offered in its support.
Where "this statement" refers to
> It’s more safe than those languages because of its type system. Memory safety isn’t the only kind of safety, after all. :)
To paraphrase, you did not have proof that Rust had features that enabled safety beyond memory safety. I gave you an example of such a feature, and explained how it provides a type of safety beyond memory safety.
> And the lack of evidence in this area
There's plenty of evidence. Abstractly, the "program is in invalid state but continues" is a huge error in general.
> To this end, state machine analysis offers a method to support engineering and operational efforts, identify and avert undesirable or potentially hazardous
system states, and evaluate system requirements.
I don't really feel the need to argue that state machines are a powerful tool for improving correctness of code. I think I've already addressed that Rust provides safety features that are:
a) Not easy to express in other mainstream langauges
I think we went far off topic form what I wanted to know and somehow ended up discussing the merits of FSMs, so I'd like to get back to what I consider the key point here:
* Rust provides freedom from race conditions. Deadlocks aside.
What other kinds of safety does it provide? I often see optional/result/match or generally "the type system" brought up and this is what I'm explicitly challenging.
I'm confused because the entire thread is about answering this question and I even detailed it explicitly in the previous post.
It provides the safety from invalid state transitions in the form of opt-in state machine type encoding.
What else is there to discuss? I just gave you a concrete example, which you challenged on the basis that it was not impactful, so I provided you research showing otherwise.
> And the lack of evidence in this area suggests to me that the safety benefit is indeed negligible.
It should only suggest to you that it is prohibitively expensive to control for all of the different variables in order to provide a remotely reliable quantification.
I program Python, Rust and C and I can assure you that my Rust programs certainly have the least amount of bugs. Not because Rust is a "safe" language, but because certain types of silly mistakes I tend to make in Python and C just wouldn't compile.
Aside from the language, understanding the Rust way of things certainly improved my code in Python and C too. And with "the Rust way" I mean the way you need to structure things if you don't want to end up fighting the borrow checker all the time. The concepts they use there make a lot of sense even in languages that don't enforce them and have been known before Rust was ever invented.
Edit: so in essence subjectively it makes me find more mistakes easier, it provides a beautiful environment for test driven development and working with it is a pleasure if you understood how things are meant to be done. This is all no real proof, but I certainly would feel more secure implementing critical code in Rust than I would doing the same thing in Python or C.
> because certain types of silly mistakes I tend to make in Python and C just wouldn't compile.
Python and C are similar in that regard. You can compile almost anything in them. Will then crash in runtime if not OK, gracefully for Python, the whole process for C.
Languages like Java or C# are different. Both are strongly typed, a lot of things are checked by compilers.
I believe C# is safer than Rust because native code surface is much smaller. Safe Rust is fundamentally limited by that borrow checker. Read the source of Rust standard library, you'll see tons of unsafe code everywhere. Rust developers probably did a good job verifying their implementations of stuff like Vec and HashMap, but there're less used parts of the standard library. Non-standard third-party libraries also using unsafe.
.NET doesn't need any unsafe code for such things. Even fundamental parts of the standard library, like List and Dictionary, are 100% managed code, to be specific it's C#: https://github.com/dotnet/coreclr/blob/master/src/System.Pri... Same is true for majority of third-party libraries: unsafe is supported by the languages, but using it may complicate deployment.
The Standard library has to use unsafe because of performance considerations, it is extremely well tested for that reason.
I wrote multiple projects in Rust and I never had to use unsafe even once. Arguably all these projects weren’t all that lowlevel, but why would I write something that somebody else has already implemented and tested? The beauty of Rust, after all is that you can go to the lowest level if you like to, but you can stay high level if you like and you still end up with code that is surprisingly performant.
> has to use unsafe because of performance considerations
Java and C# solved this problem without unsafe, with JIT compilers.
> The beauty of Rust, after all is that you can go to the lowest level if you like to, but you can stay high level if you like
All sufficiently complex systems are written in more than one language. Operating systems are written in C and C++, have shell scripting running on top. Videogames are written in C++, they have integrated script engines or VMs for higher-level stuff. Productivity software like MS office or AutoCAD have script engines.
No single language does the job. It’s not just performance versus safety tradeoff. These higher-level languages are much easier to learn, meaning developers are cheaper, and in many cases like VBA even non-developers can do stuff done with them. They often aren’t compiled or compile very fast, this makes iterations faster and allows interactive prototyping. They often run in sandboxes, solving security issues.
I don’t program in Rust, but I’ve read quite a lot about it. And I program C++ and C# for decades, often in the same software at the same time. IMO C++ is better at lower-level things: tooling, SIMD, manual control over RAM layout, better at creating fast data structures, OpenMP, has way better libraries (e.g. in the current project I use Eigen a lot, it’s based on C++ metaprogramming i.e. not portable to anything else). C# is better for everything else: it’s safer, has way better libraries included (e.g. async IO is awesome), is higher level, for the last couple of years it’s cross-platform and open source, and is much easier to use.
The standard library has far more unsafe code than an average Rust project. One of the major reasons why is “needs a lot of unsafe to implement” is a historical criteria for “this belongs in the standard library”.
> One of the major reasons why is “needs a lot of unsafe to implement” is a historical criteria for “this belongs in the standard library”
Custom data structures often need a lot of unsafe code. Graphs, trees and linked lists are everywhere in system programming.
You can’t add everything to standard library, too many variations.
For trees, in C++ and/or C# I’ve used red-black, B-tree, unordered, and multi-dimensional ones like BSP, k-d, and PK. Graphs are all different, depending on what you need to store (just the topology, per-node data, per-edge data), and which operations need to work fast on them. Even linked lists are different, sometimes you want single link, other times you want them to be circular instead of linear.
And there’re other pointer-based structures useful for some applications, like skip lists and tries.
Thanks to generics and the ease of publishing packages, those aren’t usually “custom” in Rust programs; you import a package that has them implemented for you. Just like you import ones from the standard library.
Even then, that’s still a very small amount of the overall code in a given project.
On modern hardware, RAM latency is often the main reason why CPU-bound code is slow. The fix is often changing RAM layout, i.e. modifying data structures. And when the code uses SIMD to process the payload portion of these structures, alignment requirements cause them to become really custom pretty soon.
> that’s still a very small amount of the overall code in a given project
That’s not universal across all software. Lately, I’m working in the area of CAD/CAM/CAE. Before that I worked on videogames, HPC, multimedia. In many of these projects, data structures were not a small amount, even relatively speaking.
Rust's type system supports any type of resource, not just memory. The same isn't true for garbage collectors (and no, "finalizers" is not correct resource management). Also, real enum types (including optional) and pattern matching. I'm sure you can come close with Java using interfaces or something, but it's going to be cludgy and tedious in practice (especially if you have primitive variants).
To be clear, I'm only stating that Rust is safer, not that safety is the end-all-be-all for every single application and you should never use COBOL or Java or even that you should prefer Rust over them in the general case or anything of the sort.
And I'm stating that I don't see why it's safer than Java, even if Java doesn't have pattern matching (it does have optional).
It is nice to be able to encode something in the type system, but one could just as well use design by contract or unit testing, and these actually do have supporting evidence in their favor. Is there even any supporting evidence that static typing results in better quality?
I think most would agree that it improves performance, makes refactoring and managing the code base easier.
Note: I'm also a fan of static type systems and of encoding relationships and requirements in them, but I have no proof that this actually improves quality. It makes things easier to reason about for me and maybe it eliminates some classes of errors, however it's not obvious that these classes of errors represent a noticeable amount of the total.
In Java, you can modify same data from multiple threads without the compiler complaining anything. In Rust that would cause a compile time error, until you synchronize the data.
Not only multiple threads, a common mistake is to modify a collection while iterating it, which can happen within a single thread (and it's even worse when it happens conditionally, so it'll only fail sometimes).
The thing I love the most in Rust is the duality between "&mut" and "&": when you have a "&mut" reference to something, you know nobody else has any "&mut" or a "&" reference to it, so you can modify the data structure without worry; and when you have a "&" reference to something, you know nobody else has any "&mut" reference to it, so you can read the data structure without worry. I wish I had something like that in Java, instead of having to sprinkle "synchronized" everywhere just in case (which not only doesn't protect against a recursive call within the same thread, but also can lead to AB/BA deadlocks... which is made so much more fun by code called from executors which have a ThreadPoolExecutor.CallerRunsPolicy, meaning not only will it happen only in production, it will only happen in production when the system is overloaded. Took us weeks of debugging to reproduce one instance of that...).
> In Rust, all the unsafety of C is just 1 small keyword away.
I really don't understand why people constantly bring this up. What's supposed to be the problem in the first place?
Want a safe program? Just don't use "unsafe". Perfectly doable for the vast majority of programs.
Java has its "sun.misc.Unsafe". In addition for being unsafe by default when it comes to modifying same data between threads. It doesn't enforce synchronization, so Java programs incorrect in this aspect compile just fine.
> It's very hard to integrate Java and C. People don't normally do unless they have to. Random third-party libraries from internets usually don't.
I don't see how this is related to the discussion.
There’s a lot of unsafe code already in standard library, even more in third-party libraries.
For this reason, I think VM-based runtimes are safer in general. It’s not free like in Rust, costs runtime performance especially startup time. But for Rust, we have to trust authors of standard library, and all other libraries we consume. That’s too many people to trust, and too much code to verify.
> I don't see how this is related to the discussion.
Security boundary is lower on the stack, attack surface is much smaller at that level. We only have to trust/verify JVM code. Everything that runs on top of that, including the standard library, runs in a strong sandbox.