← Back to context

Comment by procaryote

1 day ago

> You may have never written a memory bug in your life, but I have, and that renders me completely incompetent.

This feels overly binary. Memory management bugs is just one class of bugs, and there have been many other bugs leading to security issues or defects.

If you apply the standard "has ever written a bug" → "completely incompetent" you will have to stop using software, and if you think about it most other technology too

Memory safety is a very useful trait for a language though, and as you say provided by a whole bunch of different languages nowadays

Even the statement that (100% safe) Rust does not have memory bugs/mutable aliasing is not always true.

It's well known that Rust has difficulty representing graph-like memory structures, and people have taken to using arrays of `Node`-s to represent graphs, where each graph edge represents a pointer to another node.

This both efficient, and fast, but this approach sidesteps the borrow checker.

If you had a method that 2 mutable `Node` references as parameters, the borrow checker would complain if they'd point to the same struct. If you pass 2 ints, it won't.

Likewise, since liveness is tracked by user logic, you can refer to stale, deallocated `Node`-s or ones that haven't been initialized yet.

I've had people argue this is not a true memory bug, since you're not causing 'real' memory faults, but in C, `malloc` is just a function that hands you pointers into chunks of pre-allocated memory space most of the time, when it doesn't have to ask the OS for more.

I know from experience some people see this criticism as an attack on their favourite language and instantly rebuke it.

But I'd like to argue that there's something there, and it bears thinking about how 'memory allocation exisitng outside Rust' and 'memory allocating existing inside Rust' behave differently might be seen as an interesting dicothomy that needs to be resolved and that resolution might improve Rust's (or some successor language's) memory model.

  • The difference is the checking, and actual enforcement of it.

    Go and use get_unchecked if you want to and get C like behavior. But the safety note tells you the potential issues:

    Safety

    Calling this method with an out-of-bounds index is undefined behavior even if the resulting reference is not used.

    You can think of this like .get(index).unwrap_unchecked(). It’s UB to call .get_unchecked(len), even if you immediately convert to a pointer. And it’s UB to call .get_unchecked(..len + 1), .get_unchecked(..=len), or similar.

    https://doc.rust-lang.org/std/vec/struct.Vec.html

I guess parent argues that:

  - humans have a track-record of writing memory bugs

  - memory-safe languages prevent such by construction

Therefore, what's the justification of not using a memory-safe language (as opposed to an unsafe one)?

  • > what's the justification of not using a memory-safe language

    Use Go, Java or Fil-C, and memory safety is achieved at the expense of runtime performance. Tracing garbage collectors make your programs run slower and use more RAM.

    With Rust you pay with complexity. Rust has new, weird syntax (lifetimes, HRTB, etc) and invisible borrow checker state that you've gotta understand and keep track of while programming. Rust is a painful language to learn, because lots of seemingly valid programs won't pass the borrow checker. And it takes awhile to internalise those rules.

    I personally think the headache of rust is worth it. But I can totally understand why people come to the opposite conclusion.

    • > because lots of seemingly valid programs won't pass the borrow checker

      Some straight-up valid programs as well

> Memory management bugs is just one class of bugs

It's a particularly bad one though because it always leads to UB, which means you can't say anything about what happens next.

That's why memory bug severity is often "MAY lead to RCE but who knows". At least with non-UB bugs you can reason about them.

In any case, Rust massively helps with logic bugs too. It's not just about memory safety.

  • > It's a particularly bad one though because it always leads to UB, which means you can't say anything about what happens next.

    This is also why memory safety is table-stakes when it comes to formal verification of the underlying program logic. You can't solve logic bugs (even where that's known to be feasible, such as for tightly self-contained, library-like features) without solving memory safety first.

  • > it always leads to UB, which means you can't say anything about what happens next.

    If you read a language standard and try very hard to forget that the actual computer exists, sure.

    If you remember computers are real, you can pretty easily tell what will happen when you write to address 0x00000000 on a CPU with virtual memory.

    • Do note that with modern compilers it's surprisingly hard to accidentally do something that is always guaranteed to write to 0. Because it is UB, and an optimizing compiler is allowed assume that it doesn't happen. This can lead to seemingly crazy things like a variable that is set to zero, and when you deref through it it gives you something completely different instead. Because if a variable is first set to zero in all code paths, and then complex logic usually sets it to something else, after which it is dereferenced, the compiler is allowed to notice that the path where it is accessed without being first set to something else never happens, and then it is allowed to notice that the first write to the variable is dead because it's never read before being set to something else, and thus can be eliminated.

      6 replies →

    • Not all memory bugs result in writing to a null pointer.

      For example, you can do a double free, or write to a pointer that was freed.