Hacker News new | ask | show | jobs
by pdimitar 756 days ago
C/C++ being non-opinionated is the main source of the security vulnerabilities.

Let's face it, it felt good to be a lone cowboy carrying a lot of responsibility and knowing what you are doing. I was there myself and I'll admit the ego trip was awesome.

These times are long past and naturally, people refuse to adapt.

> Unfortunately you can't just have Rust's safety checks, without opting into restrictions that Rust designers force onto You that aren't inherent to safety checks, but more because 'that's a better practice (according to us)'.

Show me something that does better and I'll switch tomorrow. But don't tell me C/C++ are better -- they are not. Too much freedom leads to CVEs literally every month somewhere and that's only because we don't have better vetting and checking tools, otherwise I'm sure we'd be getting one every day for a while.

> And also, easy and fast iteration just isn't there, both because of borrow checker restrictions and compile times

I agree on that, that's why I mentioned Golang. Most of the C/C++ systems I worked on around 15-20 years ago didn't need the close-to-the-metal speed because at least 90% of their time was spent on I/O... frak, even Python would have done well there. And Golang is times faster. It's a very nice compromise if you want to be productive and don't care super much about CPU speed efficiency.

6 comments

> C/C++ being non-opinionated is the main source of the security vulnerabilities

This. "Undefined behavior" is such a terrible way of thinking. As is the "we can assume in the optimizer that UB does not happen and then eliminate code on that basis", which allows the compiler to introduce bugs that only appear at certain -O levels.

It took decades to get them to define arithmetic as twos-complement.

> It took decades to get them to define arithmetic as twos-complement.

I'm not sure this is right? IIRC C++20/C23 require two's complement representation for signed integers but generally leave other behaviors (including signed overflow) the same.

[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p09...

I’d say the biggest problem of interpreted, JIT compiled, and GCed languages is not the speed; it’s the RAM use. I agree that quite often we don’t need the speed of close-to-the-metal. But there is something wrong with most programs eating tens or hundreds of megabytes, without doing much.
And I agree on that. My favorite Elixir's runtime (Erlang's BEAM VM) still has some subtle problems with holding on to big(ger) binaries (strings) that requires very specific code be written at the green thread boundaries and it can get pretty maddening if you don't get it right -- which is not easy.

Golang I hear is doing much better btw. But you still have to be wary of its footguns i.e. leaking goroutines.

Go is doing much better on memory use because 1) it is AOT compiled, 2) it tries to allocate as much as possible on the stack (escape analysis), 3) it supports value types (the items of an array and the fields of a struct are contiguous in memory). But it's still a GCed language, which generally means a slightly higher memory use, controlled by $GOGC, to not spend too much time CPU time on garbage collection. Overall, Go is an excellent tradeoff for most applications.
There are restrictions out on You by the borrow checker to ensure safety, and then there are restrictions put on You by rust design team 'just because'.

Again, the former are fine, it's the latter I have a problem with.

In order for me to agree on the "just because" part you'll have to give some examples. What made you think they are arbitrary? And how did they prevent you from doing your job?
Comment above, mentioned borrowing while structs instead of borrowing memory. I believe this was once discussed under term 'partial borrows', but the "idiomatic" aproach is to 'just split your structs'.

Which isn't really a good aproach to structuring codebase, it's just to appeal to borrow checker inflexibility.

Lack of global scope. Lack of function overloading.

> Comment above, mentioned borrowing while structs instead of borrowing memory. I believe this was once discussed under term 'partial borrows', but the "idiomatic" aproach is to 'just split your structs'.

You're mistaking "idomatic because it's the only way" with "idomatic because we say say so". There is currently no way in Rust to specify the granularity of a borrow, so we're stuck with splitting your structs to get around it.

Part of the problem is that it's a hard problem to design around. It can not be done automatically by the compiler, because it would result in changes in the implementation being changes in the type signature. For example, say we have this function:

    pub fn foo(&self) -> i32 {
        self.a + 5
    }
The granularity is borrowing `a`. If we then change it to this:

    pub fn foo(&self) -> i32 {
        self.a + self.b
    }
The granularity of the borrow has changed in a backwards incompatible way (it now includes `b`), but that change is not reflected in the signature. It's the same reason why the compiler refuses to infer signature lifetimes from function bodies now.

You could, of course, say that we can allow the programmer to specify it manually:

    pub fn foo<'borrow>(&'borrow self) -> i32
        where 'borrow: 'self.a + 'self.b
    {
        self.a + self.b
    }
But this is now leaking implementation details if `a` or `b` are private fields.
That's truism, ofcourse there is no way to do something that is not impemented.

The question is whether or not the fact of it not being implemented comes from problem difficulty, or designers explicit refusal to do so

> Lack of function overloading.

Isn't there at least some technical basis for this (less-than-ideal interactions with type inference IIRC)?

I would say until C++98, C++ used to be more opinated, one of the reasons many of us went with C++ when given the choice, wasn't OOP features, rather the security improvements over bare bones C, with compiler provider frameworks.

Then eventually C++ got invaded by C expatriates, and writing C with C++ compiler idioms increased instead of going away.

It is like giving Typescript to groups of folks that insist on using any all over the place.

Actually I have good-ish memories of the early `boost` (we're talking 2003 - 2006) and some of the `std::` libraries in C++. They got the job done fine and were not in the way. So yeah, agreed.
>Show me something that does better and I'll switch tomorrow.

There's no limit to perfection, but if you merely don't write C of opportunistic kind, logical errors quickly start to outweigh other types of errors.

Are you willing to die on this hill? I remember an HN post a while ago where both Microsoft and Google said something like 65% of the bugs in C code were related to memory (un)safety.
I don't deny that.
> Show me something that does better and I'll switch tomorrow

Frankly this is a very bad decision. Because now you have C code, Rust code, and yet another language's code, and you're left with a mess that you have to integrate too.

Obviously I was demonstrating that I don't shill for Rust in particular -- I simply believe C/C++ are not cutting it anymore. Rust is not perfect but it solves a sizeable chunk of their problems, that's all.