back
155 comments
Some other links links on C3 that might be interesting:

Interviews:

- https://www.youtube.com/watch?v=UC8VDRJqXfc

- https://www.youtube.com/watch?v=9rS8MVZH-vA

Here is a series doing various tasks in C3:

- https://ebn.codeberg.page/programming/c3/c3-file-io/

Some projects:

- Gameboy emulator https://github.com/OdnetninI/Gameboy-Emulator/

- RISCV Bare metal Hello World: https://www.youtube.com/watch?v=0iAJxx6Ok4E

- "Depths of Daemonheim" roguelike https://github.com/TechnicalFowl/7DRL-2025

Tsoding's "first impression" of C3 stream:

- https://www.youtube.com/watch?v=Qzw1m7PweXs

C3 looks promising, but any language that supports nulls needs null-restricted types, not whatever those contract comments are. If I wanted to have to null-check everything, or YOLO it, I would just write Java... and even Java is seeking to fix this: https://openjdk.org/jeps/8303099
It's an interesting problem. Originally I experimented with both having `` and `&` syntax, so `int&` being a ref (non null) and `int` being a pointer. The thing you notice then are two things:

1. You want almost all pointer parameters non null.

2. Non-null variables is very hard to fit in a language without constructors.

Approaches to avoid constructors/destructors such as ZII play very poorly with ref values as well. What you end up with is some period of time where a value is quasi valid - since non-null types need to be assigned and it's in a broken state before it's initially assigned.

It's certainly possible to create generic "type safe" non-null types in C3, but they are not baked into the language.

Nim solves this problem by only having two explicit, restricted nullable types: Pointers and references. Pointers are manually managed, references are automatically managed, both start as nil and must have their referenced objects instantiated manually.

The entire rest of the language is built on pass-by-value using stack values and stack-managed hidden unique pointers. You basically never actually need to use a ref or a pointer unless you're building an interface to a C or C++ library. I having written a 40k line production application with no reference or pointer types anywhere. Almost any case you'd need is covered by simply passing a compound type or dynamic container as a mutable value, where it's impossible to perform any kind of pointer or reference semantics on it. The lifetime is already managed, so semantically it's just a value.

I'm on the fence about function contracts like this. I've seen them for a decade in other languages, but never really used them, so I can't say how I feel about them.

But having them be inside comments is just weird.

Why is there only one way to solve a problem?
After using Rust on a couple of projects, I understand the appeal of simpler languages like C3, Zig, and Odin. As one commenter very aptly put on the Zig subreddit ... "I used Zig for (internal tool) because I wanted to quickly write my tool and debug it, and not spend all my time debugging my knowledge of Rust."
Is Zig really that common at this point that you'd feel comfortable using it for a work project? Its not just going to piss off the next person and have them need to rewrite it? I guess Rust has the same problem to some extent but there is a lot of resources for writing Rust out there now
Based on this comparison :

https://c3-lang.org/faq/compare-languages/

One would argue that the best C/C++ alternative/evolution language to use would be D. D also has its own cross-platform GUI library and an IDE.

I wonder for which reasons D doesn't have a large base adoption.

I can only speak for myself:

1. It is so big.

2. It still largely depends on GC (less important actually)

It keeps adding features, but adding features isn't what makes a language worth using. In fact, that's one of the least attractive things about C++ as well.

So my guess:

1. It betted wrong on GC trying to compete with C++.

2. After failing to get traction, kept adding features to it – which felt a bit like there was some feature that would finally be the killer feature of the language.

3. Not understanding that the added features actually made it less attractive.

4. C++ then left the GC track completely and became a more low level alternative to, at which point D ended up in a weird position: neither high level enough to feel like a high level alternative, nor low level enough to compete with C++.

5. Finally: the fact that it's been around for so long and never taking off makes it even harder for it to take off because it's seen as a has-been.

Maybe Walter Bright should create a curated version of D with only the best features. But given how long it takes to create a language and a mature stdlib, that's WAY easier said than done.

Interestingly there is also C2: http://c2lang.org
There's also C4, but that's either an explosive or a notation language for modeling software architecture.
Yes, C3 started as a variant of C2.
Has anyone tried both C3 and Hare[1]. How do they fare? There seems to be quite the overlap between the two.

[1] https://harelang.org/

Problem with Hare is that it is (or at least was last time I checked) Linux/Unix only and so by design. That kinda makes it DOA for many.
There's also Zig in the C-alternatives space.

https://ziglang.org/

Strikes me as so so.

defer is the kind of thing I would mock up in a hurry in my code if a language or framework lacked the proper facilities, but I think you are better served with the with statement in Python or automated resource management in Java.

Similarly I think people should get over Optional and Either and all of that, my experience is that it is a lot of work to use those tools properly. My first experience with C was circa 1985 when I was porting a terminal emulator for CP/M from Byte magazine to OS-9 on the TRS-80 Color Computer and it was pretty traumatic to see how about 10 lines of code on the happy path got bulked up to 50 lines of code that had error handling weaved all around it and through it. When I saw Java in '95 I was so delighted [1] to see a default unhappy path which could be modified with catch {} and fortified with finally {}.

It's cool to think Exceptions aren't cool but the only justification I see for that is that it can be a hassle to populate stack traces for debugging and yeah, back in the 1990s, Exceptions were one of the many things in the C++ spec that didn't actually work. Sure there are difficult problems with error handling such as errors don't respect your ideas of encapsulation [2] but those are rarely addressed by languages and frameworks even though they could be

https://gen5.info/q/2008/08/27/what-do-you-do-when-youve-cau...

putting in ? or Optional and Either though are just moving the deck chairs on the Titanic around.

[1] I know I'm weird. I squee when things are orderly, more people seem to squee when they see that Docker lets them run 5 versions of libc and 7 versions of Java and 15 versions of some library.

[2] Are places where the "desert of the real" intrudes on "the way things are spozed to be"

C3 error handling is fairly novel though. It tries to find a sweet spot between composability, explicitness and C compatibility.

The try-catch has nice composability:

    try {
        int x = foo_may_fail();
        int y = bar_may_fail(x);
    } catch (... ) {
        ...
    }
Regular Result types need to use flatmap for this, and of course error codes or multiple returns also struggle with this. With C3:

    int? x = foo_may_fail();
    int? y = bar_may_fail(x);
    if (catch err = y) {
       ...
       return;
    }
    // y is implicitly unwrapped to "int" here
This is not to say it would satisfy you. But just to illustrate that it's a novel approach that goes beyond Optional and Either and has a lot in common with try-catch.
I honestly still don't know what `with` does in python. Without looking it up: Since I don't use python all that often, my best guess is that it calls some magic dunder function? I get that "primitives" like +, - aren't actually and ALSO call dunders, but there's a bit of "ssh don't tell me that and let me pretend" in the python ethos, and writing your own dunder function for anything that isn't number-ish is probably a huge code smell, and probably a potential footgun even if it is numberish. which is why `with` always felt weird to me.
Will everything blow up when they create C4?
Upvoted for humor.
a nitpick:

a bit down the page there is stuff on the case syntax. The fact that "you can't have an empty break" is a good choice, but the fact that having two cases do the same thing has syntax

    case X:
    case Y:
is footgun waiting to happen. I would strongly suggest the authors of C3 make stacking cases look like this:

    case X, Y:
"case X, Y" works for 3-4 values, but for something longer problems accumulate:

    case SOME_BAD_THING, SOME_OTHER_CONDITION, HERE_IS_NUMBER_THREE:
        foo();
        int y = baz();
Placing them on the next row is fairly hard to read

    case SOME_BAD_THING, SOME_OTHER_CONDITION, 
      HERE_IS_NUMBER_THREE, AND_NUMBER_FOUR, AND_NUMBER_FIVE,
      AND_THE_LAST_ONE:
        foo();
        int y = baz();
In C I regularly end up with lists that have 10+ fallthroughs like this, because I prefer complete switches over default for enums at least.

    case SOME_BAD_THING:
    case SOME_OTHER_CONDITION:
    case HERE_IS_NUMBER_THREE:
    case AND_NUMBER_FOUR:
    case AND_NUMBER_FIVE:
    case AND_THE_LAST_ONE:
        foo();
        int y = baz();
  
I understand the desire to use "case X, Y:" instead, and I did consider it at length, but I found the lack of readability made it impossible. One trade off would have been:

    case SOME_BAD_THING,
    case SOME_OTHER_CONDITION,
    case HERE_IS_NUMBER_THREE,
    case AND_NUMBER_FOUR,
    case AND_NUMBER_FIVE,
    case AND_THE_LAST_ONE:
        foo();
        int y = baz();
But it felt clearer to stick to C syntax, despite the inconsistency.
seems subtle to distinguish between case 3,4: for values 3 or 4, and case (3,4): for an array with the value [3,4]
I love this.

But this was distracting:

> Macros are a bag of worms. Sure, they can be a great source of protein, but will you really see me eating them? I might use worms when I'm fishing, but I don't see much use for them around the home. To express my opinion outside of a metaphor: macros have niche use cases, are good at what they do, but shouldn't be abused. One example of this abuse would be making a turing-complete domain-specific language inside of some macro-supporting programming language.

I only wish that the syntax was changed to make it easier to search/grep for the definition of functions and types. Odin makes this so nice, you can search for “<function|type name> ::”. Maybe moving the return type to after the closing parenthesis would be enough?

2 more wishes: add named parameters and structured concurrency and I think it would be a very cool language.

It was the minimal change from C. It's fairly easy regex out the types, so while not as nice as Odin, it should be straightforward.

Named parameters are already in the language.

Regarding concurrency, I don't want to pick a single concurrency model over another. I will see what hooks I can make for userland additions, but the language will not be opinionated about concurrency.

I wish C3 has simple RAII/object/class built-in(no inheritance needed, no Polymorphism is fine, just some Encapsulation better than c's struct with function pointers), then it becomes a more powerful c, and a much simpler c++, really a sweet spot in the middle of both and works for 90% of the c/c++ use cases.
Hasn't this been done already? C with classes I mean. eC and others.
There was already a better evolution of C called clay. It had templates and ownership. It was C compatible and could be used as a substitute.

https://github.com/jckarter/clay/wiki/Clay-for-C---programme...

It might be interesting to note that none of the C alternatives: C3, Zig, Odin, Hare, Jai use ownership nor RAII.

Overloading is also generally missing from today's breed of C alternatives.

There has certainly been many attempts at C alternatives: eC, Cyclone etc etc

I wish there was a way to transpile this to C. That way it can be both an escape hatch, and a way to target unusual platforms not directly supported by C3 lang.
A C backend is planned.
Anyone know the story behind Huly (which appears to be a company making a web-app mostly in node) sponsoring C3?
As far as I am aware, they support open source as part of their marketing campaign, smart move in my view and helps grass roots projects, Win win.
This looks promising, but I wonder what advantages it has over Rust. Community support is very important for a programming language, and given that this is the first time I am hearing about this project, it still has some way to go.

Edit: ABI compatibility & two way interop with C seems to be a pretty big selling point!

I want to second this comment.

The comparison shouldn't be with C, it should be with C++, Rust, or Zig.

The place to go is actually the C3 comparison page:

https://c3-lang.org/faq/compare-languages/

There you can see that there are very few items "in C3 but not Rust", for example. Mainly "it's a familiar C-like language".

I am also suspicious of the macro system. I'd like more of an explanation of how it works. Especially how it relates to Zig comptime, and whether it has "hygiene" problems. Hygiene to me means: can a variable name in a macro expansion refer to a variable outside of the macro? (The concern is that this could be accidental.)

https://c3-lang.org/generic-programming/macros/

Community support in C3 is massive, as you can use C libraries directly, it might parallel or exceed Rust on that metric, and the barrier to adding native C3 wrappers or versions is significantly lower too.

Rust is solving a different problem, that of safety over all else. C3 on the other hand is more akin to developer experience above all else.

If you find something that should be easier to do in C3, that's a bug.

Rust is a C++ competitor with all the semantic complexity that comes with it. And similar compile times.

C3 is more complex than C (because of a net increase of features), but it's miles from C++ and Rust in complexity and it compiles as fast or faster than C.

Rust is way more complex to say the least, in fact it's the sole reason why it still has the same market share as COBOL(https://www.tiobe.com/tiobe-index/). Rust 1.0 was released 10 years ago by the way.
How does it compare to to Zig ?
> Don't misunderstand me - I love using foreach in other languages; the added syntax better expresses your intent, reducing logic errors. It did jump out at me as "this isn't C" though.

Because it's not. The whole point of C is that you know exactly what's going on and it's relatively clear in the code itself. C++ hides logic in abstractions for the sake of convenience. This is a C++ thing. How does it know how to iterate? Is it moving pointers or indexing them or what? Not only is it hiding logic but it also prevents me from modifying the logic. I could easily change a C for loop to use i += 2 instead of i++ if I wanted, that's the beauty of it. With this, I have to read some docs first to see how their abstraction works, and then hope it allows me to modify how it's used to how I need.

> The whole point of C is that you know exactly what's going on and it's relatively clear in the code itself.

Given the widespread undefined behavior and the ways that compilers aggressively rely on that to reorganize and optimize your code, that hasn't been the case for many many years.

Sure, if you're using dmr's compiler on a PDP-11, then C is a pretty transparent layer over assembly, which is itself a fairly thin layer over the CPU. But today, C is an ambiguous high level communication language for a highly optimizing compiler which in turn produces output consumed by deep pipeline CPUs that freely reschedule the generated instructions.

> I could easily change a C for loop to use i += 2 instead of i++ if I wanted, that's the beauty of it.

If you're not doing something for each element of a collection, you should not be using a `foreach` loop. In exchange for not exposing the implementation, you immediately know the behavior. You also don't have to worry about checking the rest of the loop body for later mutations.

Most of these features have been used by countless C++ developers for the past decades -- I really don't see the point in adopting a language that's mostly C++ but without some of the warts. Either pick C++ or something like Rust.