8 ms·
> I believe memory safety is table stakes I'm not sure what that means. Java lets you do many things programs may want to do in a memory-safe way but not every
by pron 3mo ago
> I believe memory safety is table stakes
I'm not sure what that means. Java lets you do many things programs may want to do in a memory-safe way but not everything. Rust lets you do fewer things than Java in a memory-safe way, but more things than Zig. Zig lets you do fewer things in a memory-safe way than Rust, but more than C. So among these four languages we already have four levels of memory safety, none of them is 100%, all of them give up something in exchange for what they offer, and different programmers have different preferences for the compromise they prefer, and even that preference is context-dependent. Which of those less-than-100% memory safety compromises is the table stakes? And given that all of these compromises require something that could be quite substantial, depending on the circumstance, in exchange, and consequently programmers with the highest level of knowledge and expertise choose every one of those four in different situations, to me it seems pretty obvious that none of these is "table stakes".
- saghm 3mo ago> I'm not sure what that means. Java lets you do many things in a memory-safe way but not everything. Rust lets you do fewer things than Java in a memory-safe way, but more things than Zig. I don't think I agree with this framing. The question to me isn't "what can you do while being memory safe", it's "can you accidentally do something memory unsafe without noticing?" Rust and Java are the same here; you need to explicitly opt into using the language's mechanism for relaxing restrictions (Rust's `unsafe` blocks, Java's `Unsafe` class APIs), whereas from what I understand, neither Zig or C offers anything strict in that way.
- pron 3mo agoThat framing may seem intellectually satisfying, but it's not useful in practice. Consider the extreme edge case of C: We can clearly mechanically delineate between the empty program and a non-empty one, we call the empty program safe and any program that isn't empty unsafe (i.e. C is memory-safe if you want to do nothing and not if you want to do anything). And so, we also have this property that in C you can't do anything unsafe without noticing. Now, that's ridiculous, but something not too different happens to me with Rust. 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. Of course, other people may want to reach for a low-level language in other situations and their perspective could be different, but if I pay the price and get little in return I can't see how that would be "table stakes". Table stakes imply some universality that is obviously not here.
- saghm 3mo ago> That framing may seem intellectually satisfying, but it's not useful in practice. 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".
- pron 3mo ago> 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'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.
- steveklabnik 3mo agoI'll say the same thing I said to you as I said to Andrew, last time he and I talked about this: the way that everyone talks about memory safety (with maybe two exceptions, one okay (go) and one I dislike (fil-c)) is that "memory safe language" is about there being a clear delineation between what is memory safe and what is not, and that the unsafe aspect is a superset. Rust and Java both are memory safe, except where explicitly demarcated as not (unsafe in Rust, JNI or sun.misc.unsafe or whatever in Java). Zig and C have no such separation. When I (and others) talk about wanting memory safety, this is the important aspect of the design. This is what enables the "I know statically that a large part of the code is safe, and I also know where to check if something goes wrong" aspect of things.
- mi_lk 3mo agoWould you mind sharing some thoughts about fil-c? AFAICT its claims mostly check out so besides implementation details (GC?) it seems directionally good.
- steveklabnik 3mo agoI'll come back and say some more in a bit, but in short: I like the idea of fil-c, but I also have some issues. I think it's overall a good and interesting project, but with some caveats.
- norman784 3mo agoNot OP, but AFAIK one big issue with FIL-C it does the checks at runtime, adding overhead, don't quote me on this, but IIRC is around 20% slower.
- josephg 3mo ago2x slower and 4x less memory efficient were the numbers I heard a year or so ago. Reaching within 20% of full native performance while using a garbage collector sounds too good to be true. Do you have any actual benchmarks?
- vlovich123 3mo agoJava and Rust have actually very similar memory safety profiles. Rust let's you within the language escape the memory safety requirements whereas Java does not but both are considered memory safe languages. Rust also enforces thread safety as well which Java does not, but the slower JVM memory model doesn't let race conditions become memory safety issues whereas Rust is lower-level like Zig/C/C++ and thus thread safety could be a memory safety issue. Zig has an identical memory safety profile to C. It has facilities to make it easier to stay memory safe, but those facilities are basically equivalent to what you have in C++ and that's equivalent memory safety profile as C. > So among these four languages we already have four levels of memory safety, none of them is 100% No, you've pretended like there's four when really it's Java / Rust which are safe by default and Zig/C/C++ which are unsafe by default. One effective metric to evaluate is memory safety per LoC. Rust is ~0.2 vulnerabilities per MLoC. Java is effectively 0. C and C++ both seem to be about 1,000 vulnerabilities per MLoC. Zig is too new and hasn't had any analysis done on it, but generously it's likely at least 10-100. So the table stakes could be defined as 1 memory safety vulnerability per MLoC.
- applfanboysbgon 3mo ago> Rust is ~0.2 vulnerabilities per MLoC. Java is effectively 0. C and C++ both seem to be about 1,000 vulnerabilities per MLoC. Zig is too new and hasn't had any analysis done on it, but generously it's likely at least 10-100. You have just described six orders of magnitude in your attempt to rebut pron pointing out the four languages have four levels of memory safety.
- joshuamorton 3mo ago"two things are within an order of magnitude, and two other things are within an order of magnitude, and those two groups are three orders of magnitude apart" does sound like two groups to me.
- applfanboysbgon 3mo ago> those two groups are three orders of magnitude apart They aren't necessarily, though. Supposing that Zig were "10 issues per MLoC" (with just as much handwaving as the original poster), it would be equidistant from Rust and C. Java may also be more than one order of magnitude away from Rust; we say ~0 but is it 0.01, 0.001, 0.0001...? And why is "1 issue per MLoC" the acceptable metric that delineates what constitutes table-stakes memory-safe language? Because it's a nice, round-sounding number? I think 0 is a nicer, rounder number than 1, so let's call only Java table stakes and condemn all other languages to the garbage bin, tradeoffs be damned. Or would you say your arbitrary delineation point is worth more than mine?
- nextaccountic 3mo agosafe Rust is actually more memory safe than Java, since it guards against data races (in Java data races are not UB, but they are still one of the worst kinds of bugs because it leads to logically impossible program states) Also note that Java has unsafe, but doesn't have the culture of plainly stating safety invariants like Rust. The unsafe features of Java are less widely used, but when they are you rarely know if a Java library has unsafe internals for performance, and if they do, it may be hard to audit
- pron 3mo ago> safe Rust is actually more memory safe than Java, since it guards against data races No, it isn't. First of all, data races in Java are memory-safe, as guaranteed by the Java memory model. Second, even if that were safer in some sense it is only by dint of restricting the capabilities of the safe subset further, thus requiring more unsafe code, not less (e.g. because of that data race restriction in Rust, benign races, which are rather common in concurrent code and are not only safe but also deterministically correct, also require unsafe code). The major challenge with safety is how to offer it without complicating the language much and not restricting the safe subset. It becomes much easier if you do one or both, and Rust compromises heavily on both, which comes at a price (correctness may suffer if the language is complicated, and safety and/or performance suffer when the safe subset is restricted). Some may find that tradeoff attractive, but it should be clear why others don't. > Also note that Java has unsafe, but doesn't have the culture of plainly stating safety invariants like Rust. The unsafe features of Java are less widely used, but when they are you rarely know if a Java library has unsafe internals for performance, and if they do, it may be hard to audit Quite the opposite. First, Unsafe is being removed altogether (https://openjdk.org/jeps/498 https://openjdk.org/jeps/498), and overall, the remaining unsafe operations are now restricted to a very narrow, well-defined set of APIs (basically FFM, the FFI API). Second, Java's integrity guarantees (you can think about integrity as generalised memory safety) - as well as requesting exceptions to them (e.g. permissions to use FFM) - are handled in a more centralised, well-integrated, and auditable way than in any other well-known language: https://openjdk.org/jeps/8305968 https://openjdk.org/jeps/8305968
- imtringued 3mo agoPointer aliasing by default was a mistake. I won't budge on this one. The borrow checker is a good idea independent of memory safety. In fact, I would say that if you think that the borrow checker is a tool to ensure memory safety and it is getting in the way of flexibility, you probably don't understand the underlying problem. The memory safety given by Rust is obviously not airtight, because the aliasing case requires either unsafe blocks or reference counting, but you fundamentally give something up by allowing pointer aliasing that you can never get back once the genie is out of the bottle. The painfully constricting flexibility so reminiscent of C that everyone else clings to is not where I want to go back to.
- pron 3mo ago> The borrow checker is a good idea independent of memory safety. In fact, I would say that if you think that the borrow checker is a tool to ensure memory safety and it is getting in the way of flexibility, you probably don't understand the underlying problem. I don't think that, but I don't think that "the borrow checker is a good idea independent of memory safety" in an unqualified way. I don't doubt its general utility for one second, but I resist adding a lot of useful constructs to the language I have some influence over because I think that every feature hurts the language a bit by adding complexity. So for every feature the tough question isn't "is it useful" but "is it worth the price?" I don't have an answer for the borrow checker (and the answer also depends on the language's intended audience) but I don't think it's an obvious yes. > The painfully constricting flexibility so reminiscent of C that everyone else clings to is not where I want to go back to. That's not what I was talking about. When I use a low-level language, I use it not for what I would call flexibility, but for what I would call control. If Rust could give me safety in the parts I'm less interested in for "free", I wouldn't care about using unsafe Rust. The safe part would be pure bonus; not worth a whole lot, but I'll gladly take it. But that's not what's happening. To support safety in the safe subset, Rust is so complicated that I pay the price whether I get to enjoy that safety or not. I think that for the safe parts, Rust would have been better off using some flavour of non-moving GC because interop between full-control low-level code and non-moving GC isn't too hard (whether tracing or refcounting; it doesn't matter all that much), which would have kept the safe part simpler. Maybe it would have looked more like Swift.
- throwaway2037 3mo ago> Java lets you do many things programs may want to do in a memory-safe way but not everything. Can you give an example of Java lets you do that is not memory-safe? Honestly, I don't really count the crazy off-heap tricks that some people use. It's almost like writing a Python library in pure C, intead of Python, then complaining that Python allows you to do non-memory-safe things.
- pron 3mo agoWith FFM you can do unsafe things, such as call native code and directly manipulate memory read from or written to by native code.
- throwaway2037 3mo agoEh, I don't really count FFM. Would you have said JNI before Java 22? FFM is little more than a convenient wrapper around the same that can be dome with JNI.
- pron 2mo agoYes, I would have said JNI (and JNI still exists). But FFM is much more than a wrapper. In most situations it offers a significant performance boost over JNI. E.g. see https://github.com/Glavo/java-ffi-benchmark https://github.com/Glavo/java-ffi-benchmark, which is a bit out of date, and FFM is even better now especially in the cases it was behind in those benchmarks. Also, the integrity story is different for them. JNI is able to arbitrarily call Java methods and read/write variables, bypassing Java's access control. FFM doesn't. So really, the only integrity issue with FFM is the native code's own memory safety. With JNI, there are bigger risks (e.g. JNI can mutate Java strings).