to all of the statistical nerds out there counting memory errors in to justify the existence of : a logic error in a system that uses explicit memory management often culminates in a memory error. That doesn't make it a memory management error, it's still a logic error, and patching it up with more memory management is over-complicated at best and silently incorrect at worst.

@namark I don't think "patching with more memory management" is accurate. Rust focuses on *correctness* of ownership and mutability, especially at compile time.

By analogy to types: a logic error in a dynamically typed language often culminates in a type confusion ("undefined is not a function"). Does it mean that statically typed languages solving it with "more types" are over-complicated and silently incorrect at worst?

@namark In a statically typed language you annotate functions with types, so that when you make a logic error that causes passing nonsense data, it will be caught at compile time.

Rust is just a logical extension of that idea. You annotate functions also with memory management rules, so that when you make a logic error that causes incorrect sharing or freeing of data, it will be caught at compile time.

@kornel yes I would not count type errors such as "undefined is not a function" in dynamically typed language to statistically evaluate the usefulness of static typing, because I know there would be a lot of false positive that are caused by logic errors and not typing errors. Type conversion is a fix for a type error but it would not fix the underlying logic error. You have to fully understand the code, follow some guidelines or get lucky to identify the logic error from the type error, even if it is reported to you at compile time.

Correctness of memory management is not correctness of program.

@namark I'm surprised you're not seeing the analogy. Do you really not think that statically typed languages reduce defects in programs?

If you had traced 2/3rds of your bugs to type confusion (two + two == "22"), wouldn't you like static typing that makes these problems literally impossible to happen?
Note that rewrite from dynamic types to static types is not merely a hack that casts values, it's a logic-bug-discovering exercise, e.g. urllib3 found *logic* bugs sethmlarson.dev/blog/2021-10-1

@namark but besides, your reduction of Rust to just memory management is still inaccurate.

Rust also has sum types. Option replaces null, and makes it a compile-time error to not check for absence of a value. Result won't let you use values without checking for error condition.

Automatic destruction means there are no risky "goto cleanup" with confused state.

Mutex wrapping its content ensures you literally can't make a mistake of accessing the data without locking the mutex first.

@namark I also disagree with the generalization that all memory errors are symptoms of logic errors. They sometimes are, but there's also a fair share of just stupid direct mistakes, like not handling integer overflow in buffer sizes (e.g. stagerfright exploit) or using different buffer length when allocating vs copying. These can be eliminated by construction (you can't have a logic bug in code that doesn't need to be written).

@kornel you are either completely missing the point, or intentionally derailing now, go read the OP, I'm talking about statistic of memory errors in c++, and it's use as an indication of how important/common memory errors that rust eliminates are. It's useless because most logic errors result in memory errors in c++. You are counting these logic errors towards your total of memory management errors. And once again logic errors are not memory errors, that's what I'm trying to explain to you, while you keep imagining that I said that all memory errors are logic errors.

@namark I understand, and I think the statistic is still valid.

1. There are memory errors that are directly related to "logic" of memory management, not a symptom of a deeper error, and fixing just memory management would fix 100% of the problem, with absolutely no hidden more problems.

2. Rust does not just focus on the case 1 as you imply. It does prevent deeper logic errors too, so it can help with ultimate root causes of logic errors that manifest as memory errors.

@namark

3. in secure contexts merely stopping exploits matters, even if it doesn't fix things properly (that's why we have sandboxes etc.). A logically buggy program that misbehaves, mangles data, and crashes is still better than a program that misbehaves and allows RCE instead of crashing.

So even if your presumption that Rust fixes merey memory errors, and lets other bugs fester behind a coat of paint, that is still a valuable improvement for safety.

@kornel now you are literally arguing that most logic errors are memory errors, that's how religious you are about your beloved language.

Here is a pseudocode example to demonstrate to you a logic error, that results in a memory error, that is not itself a memory error, or has nothing to do with memory management. This is apparently utterly incomprehensible to you:

got some resource x
if(condition)
some operation that invalidates x
use x

memory error on last line... how do you solve this problem? You could replace the first operation with equivalent that does not invalidate. You could replace x with an equivalent shared resource that does not invalidate on that operation. You may take a copy of x, and keep the original for use after invalidation. You may rearrange the code so that the second use comes before the condition. All of these options will solve the memory error, but none of them would be correct. The correct solution in this contrived example is to return early in the condition body(or add an else after the if, or whatever other branching technique you use), and never use x in that case, because it was an error condition for that particular subroutine. You will never know this from the memory error alone, you need to understand the business logic of the piece of code, to figure that out. The memory error was not the cause, or even a hint of what the problem is. In fact it was a distraction.

And fixing memory errors without fixing the logic is not better than nothing, it's worse, because it complicates the code and lets the underlying logic error "fester behind a coat of paint". Otherwise it's a segfault. Both are equally bad for security but that is beside the point. Any discussion of silent bugs vs a loud bugs is about development and maintenance, not security. For security any kind of bug in a critical part of the program is a disaster.

@namark As for your example, it is interesting, and it is what Rust addresses.

You would get an error such as "x used after a move".

Rust functions define ownership, so "some operation" could invalidate x only if took ownership. Therefore, returning in that conditional would be the only way to make it compile. Unless you decide to refactor "some operation" to borrow temporarily instead, and then the caller would have a choice.

@namark You're claiming that this complicates and distracts, but I disagree.

Functions clearly declaring if they invalidate or only view their inputs clarify their intent. They require programmers to think which is the right solution (with guidance from the compiler).

In Rust you would not get a segfault either way. Not because it'd hide the memory error, but because it'd force logically correct sharing or moving of data, according to how function interfaces are explicitly designed for.

@kornel are you even reading what I'm writing or are you just a rust commercial? I don't think I can chew it up any further for you, just try again if you will, and present a better argument, I can't engage any further with you bringing up points unrelated to what I'm saying, I'll have to just repeat myself.

@namark We're clearly talking past each other.

I think I understand your argument, but we have different perspective and assumptions, so when presented with the same information, we draw different conclusions. That's why I'm trying to add context/background to the discussion which affects my judgement, but you may find it unrelated or irrelevant, because that's not what you have based your judgement on.

@kornel you are assuming I don't know the very basics of the languages I'm talking about and spewing out unrelated marketing slogans. But alright I'll try one more time and hope you'll put a bit more effort into it. I assure you, I'm not trying to destroy you favorite language, or definitively prove that it is entirely useless (I'll do that later maybe). I'm making a very specific point. So here we go again:

Memory errors are not impossible in rust, they are possible they just happen at compile time. My example was in pseudo code and conceptual, so whether the error happens at compile time or runtime doesn't matter. My point is that the error in question has nothing to do with memory management, and cannot be solved with memory management techniques, it's a completely unrelated logic error. If you solve the memory error in the most obvious way you will not solve the logic error. You can satisfy the rust compiler, but the problem will still remain. Therefore rust by the virtue of being rust does not eliminate this class of errors that statistically you would count as memory errors in C++, because that's how they are more often than not manifested technically. If you find yourself inclined to pursue the route of "it's better than nothing" again, I addressed that when I presented the example and in the OP, so please take that into consideration and continue with arguments from that point. If you think that regardless of all of that rust is still useful, that's fine, but irrelevant.

@namark I think your example is a good demonstration of a legitimate problem.

I do agree that the solution may not be most straightforward "add return" (which is the one that Rust would suggest for invalidating function).

@kornel this is beside the main point, but you seem to imply here that rust will suggest an early return in my example, which would be absolutely bonkers, so I decided to try it out

ix.io/3JpI/rust

the early return might be obvious to you because I told you it's an error condition, but otherwise I don't think it's obvious at all.

@kornel It's also funny that the compiler chooses to explain default move semantics in such an apologetic fashion. If it's so unnatural, that you need to explain in great detail, why make it default? In c++ the move would have been explicit, no need to explain, it's there, someone wrote it so they obviously didn't want the value to be used from that point on, there is no question whether they wanted a copy or a shared state or reference semantics.

@namark Rust explains the problem using Rust's terminology. Understanding it is part of learning the language. It's "apologetic" (trying to be precise and helpful), because strict ownership is difficult for people coming from languages that doesn't enforce it.

Move by default is an excellent default. It avoids unintentional copies, without subtleties of RVO &etc.

Rust also doesn't allow an empty state after move, which catches additional logic bugs, and makes dtor codegen better.

@namark BTW, I've just caught a real logic bug using Rust's mem management feature.

let this_row = image.get_row(y);
let next_row = image.get_row(y+1);
compare(this_row, next_row);

error: can't borrow image more than once.

That's because fn get_row(&mut self, y) computed rows lazily to `self.temp_buffer` and returned that, so I got the same row twice. I knew this, because I wrote the hack myself, but I forgot about it. Rust noticed &mut image lifetime is connected to row's lifetime.

Follow

@kornel Saying where the move occurred would be enough to be precise and helpful. The apologetic part is when it goes ahead and gets insecure about Box not having an implicit copy. Because it knows value semantics are natural and easy to understand, as all literal types in all languages ever use value semantics. And it knows it chose an utterly confusing default with move semantics.
In C++ language default is value semantics, that is exactly the same as literal types, and otherwise you need to be explicit. Const reference semantics is conventional default (that is evey c++ programmer is taught to always accept function parameters as const&, unless they have a reason to not do that). Most other languages also prefer that combination (value and const ref), but decide implicitly which to use based on types. This implicit choice that is different based on type is a common source of confusion in pretty much all garbage collected languages.
Rust on the other hand, not only makes the same sort of confusing implicit decision, but also chooses the most confusing default - move semantics (for the sake of performance no less), I presume because it just can't wait to show off the error messages.

Regarding your code example. How is comparing two rows of an image a logic error? You clearly have a memory management error. The embarrassing part is that, as you say, it's an intentional hack and, with the amount of tools rust gives you to get things right, it probably took more effort than a proper solution would. Just based on the code you provided and the premise of lazy evaluation, get_row must return an object that has shared ownership of the underlying resource and unique ownership of a buffer that it will use. If that's not easy to do in rust, I don't even know what's the point of the language anymore.

Again this is clearly a memory management issue with a memory management solution. I see no logic errors in what you presented unless you want to imply that the entire image type and its interfaces are one giant logic error, just because you couldn't implement proper memory management for it. I wonder if this is how C programmers end up not being able to tell sizes of files. "Ah, that is clearly a logical impossibility, cause I can't do that easily in this handicap of a language". If the underlying resource can't be shared, or can't provide random access to rows (even if lazy evaluated), then your image should be called image_stream or something, and get_row should be called skip_rows, and rust will compiler will not teach such things.

Sign in to participate in the conversation
Qoto Mastodon

QOTO: Question Others to Teach Ourselves
An inclusive, Academic Freedom, instance
All cultures welcome.
Hate speech and harassment strictly forbidden.