Honestly, that's exactly how I feel in reverse. The framing you gave is more intellectually interesting, but it doesn't help explain the actual real-world outcomes where in practice, Rust and Java both don't have much problem with unsafety, whereas C does, and at least from what I've heard, Zig does as well.
> I reach for a low-level language when I want to do low-level things in a more convenient way than in Java, but the very things that would make me reach for a low-level language in the first place are unsafe in Rust. So in ~100% of the programs I want to write in a low-level language, Rust and Zig offer the same level of memory safety (but I need to pay a higher price for Rust). That Rust reminds me that what I want to do is unsafe doesn't help me.
I mean, sure, if you want to do things that are fundamentally not possible to validate because you think you're smart enough not to screw up, that's going to make Rust a tough sell. My issue with it is that history has shown that the best C and C++ programmers in the world still write code where memory safety rears its head, so I'm distrustful of the claim that being smart and diligent is enough to prevent the sort of bugs that we're still dealing with after half a century of us learning how not to write C. You need to have an excess of either talent or hubris to consider that a reasonably safe path, and given that the amount of talent needed is a lot higher than the amount of hubris, it seems way more likely that it's the latter.
The alternative is just learning how to write code that doesn't require expressing things in a way that can't be validated. While there are some things that fundamentally are not possible to, I'm dubious that it's anywhere close to as high as you seem to expect if your experience is that you literally can't reduce the amount of unsafe code you need in Rust below "literally my entire program is unsafe".
I'm not interested in the definition so much as I am in calling it "table stakes", and so the fact that these languages satisfy their promises is uninteresting in isolation. What matters is the value of their promises. The majority of Rust programs I see, I wouldn't have written in a low-level language, so the fact that it offers memory safety for the things I don't need it to do does nothing for me.
Now, clearly, Rust's originators didn't consider what Java offers (or at least what it offered 20 years ago when Rust was first conceived) to be table stakes or they wouldn't have wanted Rust. Java exacted some price in exchange for its memory safety that was unacceptable to Rust's originators and trumped its memory safety. But the same thing happens with Rust vs Zig. Rust exacts a heavy price for its memory safety, that - just as in Rust's case vs Java - is sometimes unacceptable. So I can't see how any of these could be "table stakes".
> I mean, sure, if you want to do things that are fundamentally not possible to validate because you think you're smart enough not to screw up, that's going to make Rust a tough sell.
What Rust can validate and what can fundamentally be validated are two very, very different things. Compared to what ATS can validate, what Rust can validate is almost indistinguishable from C. In Rust you have to do lots and lots of things that require you to be "smart enough not to screw up" that you could prove in ATS, and still no one (including Rust programmers) would say that what ATS offers is "table stakes" because, obviously, it comes at a high price that the people who choose Rust don't want to pay.
So clearly different languages offer different capabilities and charge a price for them. Sometimes the price is worth it and sometimes it isn't.
> so I'm distrustful of the claim that being smart and diligent is enough to prevent the sort of bugs that we're still dealing with after half a century of us learning how not to write C
But Java or Rust programs still suffer from a lot of bugs that ATS could eliminate, if you're willing to pay the price, and you're clearly unwilling. ATS programmers could say about Rust programmers what you say about C++ programmers. Clearly there's no universal table stakes here.
I mean, if you're already going to say "I don't want a low level language for anything other than what I can use unsafe for", then of course Rust will seem like overkill. I'd argue that the value of Rust is that it makes low-level viable for a lot of stuff that would otherwise require a lack of memory safety; a lot of it is stuff that might be written in a higher level language, but that's just because relatively few programs are impossible to write in higher level languages. That doesn't mean that the ones that need to be lower level can't be written in Rust though.
> Now, clearly, Rust's originators didn't consider what Java offers (or at least what it offered 20 years ago when Rust was first conceived) to be table stakes or they wouldn't have wanted Rust. Java exacted some price in exchange for its memory safety that was unacceptable to Rust's originators and trumped its memory safety. But the same thing happens with Rust vs Zig. Rust exacts a heavy price for its memory safety, that - just as in Rust's case vs Java - is sometimes unacceptable. So I can't see how any of these could be "table stakes".
Yes, "table stakes" is a value judgment, and one some people will disagree with. The cost for memory safety in Java is performance overhead though, and the cost for memory safety in Rust is not being able to express certain valid things that can't be validated; those are both objectively different from not offering memory safety at all, and my point is that the cases where what you want to express is literally impossible in Rust to do safely while actually being memory safe are pretty rare. There are some cases where what you're trying to do are fundamentally unsafe, in which case you need to use an unsafe block, but that's not anywhere close to the same as removing validation from the entire program. I'm fairly skeptical that you're basing your view that there are so many cases where you want to do something that's guaranteed to be safe but impossible to write in safe Rust on objective criteria, and extremely skeptical that the programs you write are anywhere close to entirely comprised of logic that can't be expressed safely.
> What Rust can validate and what can fundamentally be validated are two very, very different things. Compared to what ATS can validate, what Rust can validate is almost indistinguishable from C. In Rust you have to do lots and lots of things that require you to be "smart enough not to screw up" that you could prove in ATS, and still no one (including Rust programmers) would say that what ATS offers is "table stakes" because, obviously, it comes at a high price that the people who choose Rust don't want to pay.
> So clearly different languages offer different capabilities and charge a price for them. Sometimes the price is worth it and sometimes it isn't.
Sure, no one is disputing that. But that doesn't change the fact that some languages objectively require you to opt into which parts are memory unsafe, and others don't. It's obvious we won't see eye to eye on whether that's table stakes or not, but that's a difference of opinion, and having a different opinion than you isn't literally illogical; I find your take on it to be as hard to understand as mine is to you.
> But Java or Rust programs still suffer from a lot of bugs that ATS could eliminate, if you're willing to pay the price, and you're clearly unwilling. ATS programmers could say about Rust programmers what you say about C++ programmers. Clearly there's no universal table stakes here.
You're again taking an empirical argument as an abstract one and ignoring the real world outcomes that languages produce. You mentioned finding the fact that they actually produce real world software that in practice do not suffer from the class of bugs that C/C++ suffers from uninteresting, and that's fine, but it's meaningful for people who care about software actually getting used in the real world for real things. You seem to be arguing that unless you can eliminate literally the most bugs of any language in existence, then eliminating any bugs by picking a language that eliminates some of them is a useless endeavor. To me, the reasonable thing would be to choose a place to draw the line and say "anything beyond this is too risky, but I'll tolerate anything that's at least this safe", and memory safety is in practice the place I think it makes sense to do. I don't agree at all that not drawing any line at all is the only logical choice in a scenario when there are multiple places to draw it.
Maybe, but I don't see making a low language viable for something it's not needed as offering much value. Low-level languages are primarily designed to give you direct, low-level control over interaction with the hardware, they sacrifice other things for that goal (including performance [1]), and so if I don't need that control I don't use a low-level language. When I do need that control, I find that Rust requires reaching for unsafe too frequently while still paying the full price for the safety of things I don't use (even Rust's memory management of strings doesn't give me the control I want; I have to work pretty hard for it).
> The cost for memory safety in Java is performance overhead though,
It's not performance (you often gain performance, especially in large programs). It's warmup and footprint.
> But that doesn't change the fact that some languages objectively require you to opt into which parts are memory unsafe, and others don't.
Like I said, C also fits in the category, so it's not a meaningful distinction. The difference is in what you can do in the safe subset. Zig lets you do more things in a safe way than C (where the safe subset is effectively empty), Rust lets you do more safe things than Zig, and Java lets you do more safe things than Rust.
> You mentioned finding the fact that they actually produce real world software that in practice do not suffer from the class of bugs that C/C++ suffers from uninteresting
I didn't say that that's uninteresting; in fact Zig also eliminates spatial unsafety as well as Rust, and I think that's good. I said that merely looking at broad statistics is uninteresting if you don't consider the kinds of programs being written. I.e. Rust gives me safety mostly when I write code with the same level of low-level control as I have in Java, then that's the part I find interesting.
> You seem to be arguing that unless you can eliminate literally the most bugs of any language in existence, then eliminating any bugs by picking a language that eliminates some of them is a useless endeavor.
That's the very thing I'm arguing against. I'm saying that different languages eliminate different bugs at a cost (again, Zig eliminates many memory safety bugs you'd find in C or even C++, arguably the most dangerous ones). What I'm saying is that what you get and whether the price is worth it depends both on the program you're writing and on your personal preferences. Just to be clear, "preferences" doesn't mean I care more or less about correctness, but which approaches to correctness I find more or less effective, something on which there is no consensus.
> To me, the reasonable thing would be to choose a place to draw the line and say "anything beyond this is too risky, but I'll tolerate anything that's at least this safe", and memory safety is in practice the place I think it makes sense to do.
I think it also depends on the kinds of programs you write, because for many programs I write (and for which I pick Java) Rust's level of memory safety is too low, and for the programs I pick a low-level language I wish I could have some cheap memory safety, but it's not offered to me. So in those cases I would prefer Zig's spatial memory safety, as it's no worse than Rust, and not pay the high price for Rust's while getting little in return. Anyway, I'm saying that it's both a matter of which approach you believe leads to better correctness and the kinds of programs you write in the language.
[1]: For example, the fact that in Java, references are not required to be stable machine pointers opens the door to some powerful optimisations that are not available to languages where pointers are required to be machine pointers (or something close enough to them). Or the fact that low-level languages require that the machine instructions executed are those present in the compiled image (or close enough), or, if you want, caring about worst-case performance at the expense of average case performance (although both C++ and Rust specifically don't always make that easy) precludes some other very powerful optimisations. People like me who've spent years on huge C++ programs know that the low-level control offered by low-level languages (regardless of the question of safety) sometimes helps performance and sometimes harms it.
Well, Fil-C and also Cheri show that C is a language that can be implemented with perfect memory safety for 99.9% of the language. This is not true for every language but is also not an accident in C. But also with the typical implementations of C such as clang and gcc you can essentially get spatial memory easily by using safe abstractions.
First of all I respect your point of view - I'm not a Rust absolutist, I think that garbage collected languages are a massive advantage for a lot of things and would never criticise someone choosing a higher level language. Likewise I wouldn't criticise someone choosing Zig or Oden or Jai or even C for tasks where you really need that low level control.
For me, I like to have a single language that I can use for pretty much everything. Afaik there is no other language that is a) popular b) has a modern toolchain with integrated build, formatting & linting etc, and c) can be used both in the kernel and for developing websites. Rust might not be the best choice for most of the spectrum of software, but it's good enough for everything. I can write a low level service + a web server and UI in the same language, where with other choices I would need to use two separate languages. This matters to me because I don't have the time to maintain mastery of multiple languages, I find a lot of value in focusing deeply on one language and learning it completely.
Now I also don't write a lot of low level rust, I've never written a block of unsafe before and I probably write "unidiomatic" rust with too much copying, too many Arc<Mutex>>'s etc. But I like knowing that I can if I need to.
Rust has a lot of other things going for it. A good type system with plenty of nice language constructs that are missing in a lot of higher level languages. It has Cargo and a healthy ecosystem (although I do worry about the number of dependencies used sometimes). And a large community of very smart people. I'm not saying this is exclusive to Rust, but as a whole Rust is a unique language with no alternatives if you value the things I do.
So I would say that it's approach to memory safety threads the needle where it can be used (although not the very best choice) for when you'd use a higher level language, but also gives enough control that you can do plenty of low level stuff in it safely, and with clearly delineated unsafe sections where you really can do anything.
To me, those are also performance characteristics. Maybe my view on what constitutes "performance" is broader than average here.
> When I do need that control, I find that Rust requires reaching for unsafe too frequently while still paying the full price for the safety of things I don't use (even Rust's memory management of strings doesn't give me the control I want; I have to work pretty hard for it).
Fair enough, I can't tell you that you don't have that experience when writing Rust. It's pretty different from mine though, and the experience of the large number of former C/C++ devs I've worked with after they learned Rust; the only people I've talked to with that experience didn't really try to learn Rust and went in hoping that it wouldn't work for them, which informs my perception here, but I recognize that individual experiences won't always fit into larger trends.
> Like I said, C also fits in the category, so it's not a meaningful distinction. The difference is in what you can do in the safe subset. Zig lets you do more things in a safe way than C (where the safe subset is effectively empty), Rust lets you do more safe things than Zig, and Java lets you do more safe things than Rust.
I don't think I understand what you're saying here. I don't know of a way to turn off undefined behavior by default in C and only opt into it in discrete segements of the code, but maybe I'm missing something.
> That's the very thing I'm arguing against. I'm saying that different languages eliminate different bugs at a cost (again, Zig eliminates many memory safety bugs you'd find in C or even C++, arguably the most dangerous ones). What I'm saying is that what you get and whether the price is worth it depends both on the program you're writing and on your personal preferences. Just to be clear, "preferences" doesn't mean I care more or less about correctness, but which approaches to correctness I find more or less effective, something on which there is no consensus.
It seems like you're arguing against the idea of memory safety as a category at all then. To me, "I can't write code that's memory unsafe without explicitly opting into it" seems like an objective statement, and it's objectively different than "I can't write certain types of memory safety bugs in a given language". I don't really understand what's useful about being able to write memory unsafe code without having to opt in when in practice the number of bugs from mistaken memory safety are overwhelmingly more common than the cases when you're forced to opt into unsafe because Rust forced you to work around the constraints, and even in low-level programs, the actual number of truly unsafe operations you need to do tend to be fairly low in my experience. I guess I can't say for certain that you don't truly need to do things that you're forced to write unsafe for too often, but to me, it seems like you're refusing to pay a pretty small price for mostly ideological purity rather than pragmatism.
> I think it also depends on the kinds of programs you write, because for many programs I write (and for which I pick Java) Rust's level of memory safety is too low
> [1]: For example, the fact that in Java, references are not required to be stable machine pointers opens the door to some powerful optimisations that are not available to languages where pointers are required to be machine pointers (or something close enough to them). Or the fact that low-level languages require that the machine instructions executed are those present in the compiled image (or close enough), or, if you want, caring about worst-case performance at the expense of average case performance (although both C++ and Rust specifically don't always make that easy) precludes some other very powerful optimisations.
I'm struggling to imagine what the circumstances are where these are genuine concerns rather than theoretical or premature optimizations. What are some examples of programs where you'd get better characteristics running them if they were written in Java rather than Rust due to the lack of enough "memory safety" in Rust?
Yes, but they come with speed gains, so you can't say that you pay "performance overheads" when Java removes some of the performance overheads that programs in low-level languages and replaces them with others. You could similarly say that you pay performance overheads when going in the other direction.
> and the experience of the large number of former C/C++ devs I've worked with after they learned Rust
And it's not my experience or a large number of C/C++ devs I work with.
> the only people I've talked to with that experience didn't really try to learn Rust and went in hoping that it wouldn't work for them
Then your exposure isn't wide enough.
> I don't think I understand what you're saying here.
What I'm saying is that we can't say that the value is merely in the existence of a clear syntactic distinction between safe and unsafe code, because that distinction exists in C, only in C, the clearly delineated line between safe and unsafe code is that between `int main(void) {}` and anything that isn't that; i.e. any program other than that explicitly opts into unsafety. So any meaningful discussion about memory safe languages must include what you can do in the safe subset. In C's "safe subset" (the empty program), you can do nothing, and that's what makes it not valuable. But for my needs, what you can do in Rust's safe subset (compared to both Java and Zig) is also far too little (to justify the cost).
> To me, "I can't write code that's memory unsafe without explicitly opting into it" seems like an objective statement
It is, but what I'm trying to say is that it alone doesn't have much value. In C you also "can't write code that's memory unsafe without explicitly opting into it" by writing anything other than the empty program, but obviously you wouldn't consider C's memory-safe subset suitable because you can't use it to do what you want to do in C. Rust's value is not, therefore, in that it has a memory-safe subset, but that it has a useful memory-safe subset. It's just that the utility of that subset depends on the kinds of programs you'd want to use a low-level language in the first place.
> it seems like you're refusing to pay a pretty small price for mostly ideological purity rather than pragmatism.
Quite the opposite. The price of Rust's complexity, implicitness, and compilation time is too high for what little safety I get in return, that I don't want to pay it for pragmatic reasons.
> I'm struggling to imagine what the circumstances are where these are genuine concerns rather than theoretical or premature optimizations. What are some examples of programs where you'd get better characteristics running them if they were written in Java rather than Rust due to the lack of enough "memory safety" in Rust?
It's nothing to do with memory safety. Low-level programs sacrifice optimisation opportunities available to Java because above all else they need to offer low-level control. That low-level control can translate to good performance sometimes (especially in smaller programs), and sometimes it translates to worse performance (especially in large programs). The huge C++ programs I worked on migrated to Java not (just) for safety but also for better performance than C++ (again, it's easy to get excellent performance in low-level languages when the programs are small or specialised; it gets harder and harder as they grow). So we got better performance than C++ while also getting better safety than Rust, a much simpler language than Rust (or C++), and faster build cycles than Rust (or C++). But the topic of how Java reduces the overheads that C/C++/Rust/Zig programs often have when they grow large (although Zig makes it easier than the other them to reduce them) is a whole complicated topic. I might give a talk about it at the upcoming Devoxx.