Remix.run Logo
kstenerud 4 days ago

In C, sure. C is a dangerous language of its time.

But these contracts don't make things better.

Now you're removing control from the user. So now if an allocation fails, you crash. No way to recover from it. No getting an error signal back (NULL) so that you can say "OK, I need to clear up some memory and then try again". (Note that I'm not saying that inline error signaling such as NULL is good design - it's not).

Nope. No error handling. No recovery. You crash. And ain't nothing you can do about it.

That's just bad design on top of the existing bad design. Things that crash your app are bad. No need to add even more.

hdjrudni 4 days ago | parent [-]

I'm not convinced. If something is going to cause a crash, like a nullptr, I'd rather crash near where the error happened with a nice error message, than hitting some UB crash god knows where.

Do I want my app to crash at all? No, of course not. If it's crashing, there's a serious bug. At least now I know where to look for it.

Should we pass back up an error signal instead of crashing? Yes, if it all possible, do that instead. Sometimes it's not possible or not worth the hassle for something you're 99.99999% sure can't/won't happen. Or literally can't currently happen, but you're afraid someone on the project might do a bad refactor at some point 5 years down the road and you want to guard against some weird invariant.

1718627440 4 days ago | parent | next [-]

I think the point is about e.g. malloc as shown in the blog post. The stdlib function already have return values to indicate invalid arguments from the caller. These exist to allow the caller to decide what to do in this case. Replacing them by invoking UB or panicing, means the user looses control.

Having the stdlib expose both ways, means either having two stdlibs (like on MS Windows), or dynamic checks in the stdlib. I don't think either way is a good idea. Thus, these contracts can't be used by libc.

kstenerud 3 days ago | parent [-]

Precisely. The barn door is already open as far as the C library goes. And since C's error mechanism is all in-band (another bad idea in hindsight), the only thing the extra invariant checks will do is add even more (now uncontrollable) crash points to an already intolerably crash-prone app.

Panics are like goto: Only useful in VERY rare circumstances. Every use of a panic should require a rock-solid justification for why you're choosing to crash the process - similar to how every call to abort() requires justification in any professional C codebase. assert() in production code was a horrible idea because liberal use was actually encouraged.

Rust's panic mechanism was almost good. Unfortunately, they chose to give them all innocuous names like unwrap() rather than or_panic(). So now you have to check for it all the time using clippy because it's too easy for a human to accidentally gloss it over. Linting usually points to a design failure in the language UX.

bluGill 4 days ago | parent | prev [-]

The other hope for contracts is static whole program analysis can prove your program - at least in part. If you can prove some contract false that tells you where a bug is long before anyone triggers it.