Hacker News new | ask | show | jobs
by GlitchMr 2391 days ago
Wow, this is quite a trainwreck. I think it's possible to improve C while keeping compatibility with C programs, but that's not how you should do it.

I think an improved C would be quite useful, but you really should be careful with what you include it, and keep the language simple. A lot of functionality feels like added because it could be added (WTF are nested routines). This way lies worse C++.

In particular, I wouldn't look too much into what C++ does. Rather, I would look into what Go, Rust and Zig do. `?{}` and `^?{}` are clearly inspired by C++ constructors and destructors, but that's not the only way (in fact, it's a rather bad approach if you want to keep things simple). For instance, you could avoid having constructors and require full initialization for structures with destructors. Hypothetically, it could look like this.

    struct Point {
        int x;
        int y;
    };

    struct Point new_point(int x, int y) {
        return { .x = x, .y = y };
    }

Meanwhile, C-for-all is mostly missing actually useful features. Slice types (https://www.drdobbs.com/architecture-and-design/cs-biggest-m...) would be huge, but they are nowhere to be seen. Vtable dynamic dispatch? Missing. Borrow checker? Missing. Module system (maybe a stretch, but...)? Missing.
2 comments

You realize that this is (almost) valid C99?

In C99 it looks like this:

    struct Point {
        int x;
        int y;
    };

    struct Point new_point(int x, int y) {
        return (struct Point) { .x = x, .y = y };
    }
Yes. In fact that's intentional.
Who owns the return value of new_point?
It lives on the stack and is handed around by value, the question of ownership really only gets interesting once heap memory and pointers/references come into play.

I guess ancient C compilers would do a memory copy on return, but there are various optimizations in "newer" compilers which remove redundant copies, and structs up to 16 bytes or so are passed in registers anyway (via the 64-bit Intel and ARM ABI conventions).

The function that calls `new_point`, the value is moved. Similarly, consider the following case.

    struct Point a = new_point();
    struct Point a2 = a;
Assuming `struct Point` has a destructor, this would move `a` into `a2`, and prevent accesses to `a` after `a2` assignment, as `a` is no longer considered to be alive at this point. C++ has a wrong default of cloning instead of moving, but a new language could fix that.
That default changes in C++11: https://en.cppreference.com/w/cpp/language/copy_elision

The logic around it is still pretty complicated, since we’re talking about C++.

> C++ has a wrong default of cloning instead of moving

Which it inherited from C.

Why is cloning rather than moving the wrong default?
It isn't. Cloning is what actually happens under the hood, if a and a2 are in different memory locations. Making a inaccessible after the assignment is merely a convention. With a struct that only has data members, there are no further consequences.

If the struct contains a pointer, you need a kind of additional contract that says how the memory that is pointed to is going to be managed, particularly when it is going to be freed and by whom. And that is where move semantics establish a constricting convention that is intended to make that unambiguous.

So you’re looking for Rust with C’s syntax?
Would be a big improvement on Rust.
What part of Rust’s syntax do you think is a detriment vs C? A lot of it is very similar to C, but with ambiguity removed.

- Variable types are changed to avoid ambiguity in lexing.

- Loops and conditionals require {}, which means the parentheses can be dropped on the condition and there’s no ambiguity about nested if’s and else’s.

What would you like improved?