14 ms·
Show HN: C3 – A C alternative that looks like C
Compiler link: https://github.com/c3lang/c3c https://github.com/c3lang/c3c
Docs: http://www.c3-lang.org http://www.c3-lang.org
This is my follow-up "Show HN" from roughly a year ago (https://news.ycombinator.com/item?id=27876570 https://news.ycombinator.com/item?id=27876570). Since then the language design has evolved and the compiler has gotten much more solid.
Assorted extra info:
- The C3 name is a homage to the C2 language project (http://c2lang.org http://c2lang.org) which it was originally inspired by.
- Although C3 mostly conforms to C syntax, the most obvious change is requiring `fn` in front of the functions. This is to simplify searching for definitions in editors.
- There is a comparison with some other languages here: http://www.c3-lang.org/compare/ http://www.c3-lang.org/compare/
- The parts in C3 which breaks C semantics or syntax: http://www.c3-lang.org/changesfromc/ http://www.c3-lang.org/changesfromc/
- Aside from the very C-like syntax, one the biggest difference between C3 and other "C competitors" is that C3 prioritizes C ABI compatibility, so that all C3 special types (such as slices and optionals) can be used from C without any effort. C and C3 can coexist nicely in a code base.
- Currently the standard library is not even alpha quality, it's actively being built, but there is a `libc` module which allows accessing all of libc. Raylib is available to use from C3 with MacOS and Windows, see: https://github.com/c3lang/vendor https://github.com/c3lang/vendor
- There is a blog with assorted articles I've written during the development: https://c3.handmade.network/blog https://c3.handmade.network/blog
- deleted 4y ago[deleted]
- addaon 4y agoLooking at the primer... // C typedef struct { int a; struct { double x; } bar; } Foo; // C3 struct Foo { int a; struct bar { double x; } } Very confused by this. The C code declares an anonymous struct type, then aliases the typename "Foo" to that anonymous struct type. The C3 code seems to declares a named struct type "Foo" -- why isn't the C equivalent here just "struct Foo"? But then within the struct it gets weirder... the C code declares a second anonymous struct, and then declares a member variable of that type. The C3 code... declares a struct named "bar" and also a member variable with name matching the type? Except the primer says that these are equivalent, so the C3 code is declaring an anonymous struct and a member of that type? Using the same syntax as the outer declaration did to declare a named type but no (global) variable?? Is this case sensitive? I don't think I can get further into the primer than this... even taking the author at their word that the two snippets are equivalent, I don't understand what's in play (case sensitivity? declarations where variable name must match type name?) to make this sane, and there's zero rationale given for these decisions.
- addaon 4y agoOh gosh... digging further on the same page under "Identifiers" it looks like case sensitivity is the key here. So "struct Foo" declares a type "Foo", and "struct foo" declares a variable "foo" of new anonymous type. I assume "struct Foo foo" and "struct bar Bar" do exactly what you (don't) expect, and maybe even "struct foo bar baz {}" to be the equivalent of the C code "struct {} foo, bar, baz"... yikes. Edit: "Declaring more than one variable at a time is not allowed." So there's no equivalent to the C code ""struct {} foo, bar, baz"... not clear if "struct IDontNeedANameButTheLanguageIsForcingMe foo {}; IDontNeedANameButTheLanguageIsForcingMe bar; IDontNeedANameButTheLanguageIsForcingMe baz;" is legal (modulo that some of those semicolons are illegal I think?). Yeah, this needs some rigor in the docs.
- Cloudef 4y agoThis doesn't seem very well thought out at all
- deleted 4y ago[deleted]
- lerno 4y agoNo, you misunderstand. There is no `struct Foo foo`. Unlike C `struct Foo` would only ever be valid at the top level. Neither `struct Foo foo` nor `struct bar Bar` works. The reason why `Bar` is a type and `bar` is a variable or function is to resolve the ambiguity of C syntax without having to rely on unlimited lookahead or the lexer hack. Basically it allows tools to parse the code much more easily than they would C. You can declare multiple fields in a struct, e.g. `struct Foo { int x, y; }`, but you can't write `int x, y = 123;` like in C. This is because it would create ambiguities in the extended for/while/if syntax C3 adds.
- lerno 4y agoThe first part about the name is just like C++: you use the name without `struct` unlike C where structs has its own namespace. That's what it's meant to illustrate. The second question is more subtle. In C, the syntax is `struct { ... } [optional member name] ;`. Because there is no anonymous struct at the top level, the anonymous structs inside of a struct has a different syntax, also eschewing the final `;`, changing the syntax to `struct [optional member name] { ... }`. If the C syntax structure is desired a final `;` would be required. This syntax change comes from C2.
- keikobadthebad 4y ago> requiring `fn` in front of the functions. This is to simplify searching for definitions in editors. You don't have to do that even in C, you can use this style for function defs static int myfunc(...) { ... } and search or grep for ^myfunc\( to isolate the definition as opposed to the use.
- lerno 4y agoInterestingly removing `fn` was something I've considered more than once, but I had people ask me to keep it. Even though you can get searchability in C by adhering to certain conventions, that's not the same as being able to search ANY code base in a simple manner, which I believe is what people wanted.
- keikobadthebad 4y agoI doubt many C programmers were waiting for someone to force a function naming style on them after 50 years.
- mst 4y agoI think this is more aimed at people who couldn't quite get their head around C and were happy to have a forced function naming style that made tooling easier. (note, I am -shit- at C, but genuinely wonder if I could write this without spending 98% of my debugging time tracing segfaults that were down to my own stupidity)
- deleted 4y ago[deleted]
- IshKebab 4y agoOr just use an IDE. It's 2022 people! We don't have to code like it's 1985.
- Cloudef 4y agoI still haven't used a IDE that has made my developer experience better than simple text editor and some cli tools. They all seem to fail spectacularly if you have to do something bit more different than they were designed for (usually regarding how the project is built), and often feel very sluggish. Also the project files or build files they generate are usually horrible, and not great for version control.
- axy 4y agoGlanced. 17 or 18 years ago I implemented my own programming language too so I can only say: keep on. Just a quick note in terms of Art and Beauty: what I expect from a new language is expressiveness, and encouragement of good programming techniques, so that a code written by an average programmer in the worst mood would not look as a total mess.
- FpUser 4y agowhat is it with fn()? If we are so much into being terse then int abc() should be just fine. If we need readability than function() instead of fn() would do better.
- azinman2 4y agoFrom the post: - Although C3 mostly conforms to C syntax, the most obvious change is requiring `fn` in front of the functions. This is to simplify searching for definitions in editors.
- lyrachord 4y ago
- stormbrew 4y agoIf you haven't heard of it before, look up the "most vexing parse" for the kind of thing languages are trying to avoid with this syntactic approach. That specific one is a c++ thing, but C also has its own versions of it (that c++ inherits). Basically the C grammar is only context free if you cheat in the lexer, and any language trying to improve on C is likely to try to avoid that (or just give up and go wild).
- lelanthran 4y agoI interpreted the GP to mean: "Why use `fn` instead of `function`?" After all, he did say: > If we need readability than function() instead of fn() would do better.
- stormbrew 4y agoRight, I'm saying that it's not about human readability, it's about parser (and I suppose grep) readability. So the goal is to make the parse easier with as little effort on the part of the programmer as possible. The whole word function would take up an unnecessarily large part of a line for that purpose.
- deleted 4y ago
- EdSchouten 4y agoI'm puzzled with what the market for these kinds of languages are. C is sort of a dead end. There is very little innovation there. And that's fine; the users of the language seem to want it that way. They just want to write software the same way they've been doing for the last 20 years. Why would such a conservative user base want to switch to a different language like C3? Linus once said this about Subversion: "Subversion has been the most pointless project ever started... Subversion used to say, 'CVS done right.' With that slogan there is nowhere you can go. There is no way to do CVS right." Could C3 be the Subversion of programming languages?
- rwbt 4y ago> I'm puzzled with what the market for these kinds of languages are. There's a significant number of C programmers who want something slightly more modern and convenient but don't want to write C++ due to a number of reasons. I think Zig, D are examples of this niche but syntax wise they don't completely look like C.
- bachmeier 4y ago> I think Zig, D are examples of this niche but syntax wise they don't completely look like C. This is technically true, but someone that likes writing C while wanting a few extra features would mostly be able to write the same C code they always have if they stick to D's betterC. (I'm not familiar enough with Zig to comment on that.) Edit: Should also add that D now compiles C code, so a C programmer could continue to write C as they want, write a few D functions where those features help, and compile them without writing any binding code.
- com2kid 4y ago> I think Zig, D are examples of this niche but syntax wise they don't completely look like C. That is because C's type declaration format is provably, as in actually provably, terrible. There is a reason the majority of modern languages have switched away from how C declares types. Honestly it'd be nice if the C committee could find a way to tack on a new variable declaration syntax to C, so everyone didn't have to keep looking up, or using tools, to declare non-trivial function pointers.
- _gabe_ 4y agoThis looks awesome! The main thing holding me back from switching from C++ to C is the lack of type safe generic programming. This language looks like it not only solves that, but adds some other interesting features like defer that I've been wanting to try out :D. It looks like there are some examples of large projects getting successfully compiled by C3 (the vkDoom). Since this is still only alpha 0.2, I'm curious how stable the compiler is and whether the core language features are subject to change? I'd love to start using this on some projects, but I'm always afraid to adopt a language in its early stages.
- lerno 4y agoYou shouldn't use C3 on any larger project, but it could be an option for doing gamejams (with raylib or something else with a C API). For vkDoom it's not a port to C3. What it demonstrates is instead C <=> C3 interop. I removed some of the central functions from the C code and implemented those in C3. The script compiles the C files into .o files with the normal C compiler, then uses the C3 compiler to compile the .c3 files into .o files. Finally all are linked together into a single binary showing off the simple ABI compatibility story (no extra annotations are needed to ensure compatibility - all C3 functions are automatically callable as C functions) In regards to the versioning, I've gone through two versioning schemes for the pre-alpha. 0.1.0 is the first version I felt was sufficiently feature complete. Minor version changes (e.g. 0.1.x -> 0.2.x) is for any breaking changes. Minor version doesn't say how close it is to version 1.0 (minor version will continue after 0.9.x with 0.10.x). The 0.1 version was out in April. I try to make the compiler as solid as possible, but I would need thousands (rather than somewhere above 400) tests before I feel confident in the compiler. Releasing 0.1 means I think that the language design is mostly there now, so fewer changes are coming. But joining any language before 1.0 is a bumpy ride. Did that answer your question?
- _gabe_ 4y agoYep! I appreciate the thorough response and I'll be starring the project and following it's progress. It's very cool and looks great so far. Good luck with the project!
- WalterBright 4y agoShould compare with DasBetterC too! https://dlang.org/spec/betterc.html https://dlang.org/spec/betterc.html
- asveikau 4y agoThat page specifies a rationale of: code loaded from C will not load the D runtime. Is it also possible to provide a C-callable shared D library that initializes the runtime dynamically? Would you then get some missing features like global constructors? Googling around seems to suggest there is rt_init or Runtime.initialize for that? I've never used D, I'm just curious about the problem described. Ps. Goes without saying that i, like others, appreciate your comments here.
- WalterBright 4y agoThe impetus for DasBetterC is to use a subset of D that does not require the D runtime library. A surprising amount of D users like this and use it. I use it for projects where I want a small runtime executable. DasBetterC executables are as small as C ones can be.
- lwswl 4y agoAre they really? Because most c executables are almost never as small as they can be.
- mhh__ 4y agoIf the D code is aware that it needs to initialize the runtime then yes, either that you or you initialize it yourself before using it. rt_init will fire up the D runtime. As for constructors the runtime should handle those when the module is initialised, so not necessarily on program start up or the DSO being loaded. If, however, you want to register a C runtime constructor you can do do so with pragma(crt_constructor). I actually recently-ish fixed a bug to make the aforementioned type of constructor work on MacOS because Apple decided to deprecate them with no reason.
- lerno 4y ago
- tialaramex 4y agoAside - Documentation says: > It is possible to cast the variant type to any pointer type, which will return null if the types match, or the pointer value otherwise. That seems backwards to me. Maybe it's just me? Surely if the types match that's when we get the pointer value ? My main point - Strings I think at this point good strings are table stakes. If you're a general purpose language, people are going to use strings. Are they going to make a hash table of 3D vectors? Maybe. Are they going to do finite field arithmetic? Maybe. Are they going to need networking? Maybe. But they are going to use strings. C's strings are reprehensibly awful. C++ strings are, astoundingly, both much more complicated and worse in many ways, like there was a competition. You need to be doing a lot better than that IMHO. I happen to really like Rust's str but I'm not expecting to see something so feature rich in a language like C3. I am expecting a lot more than from a fifty year old programming language though.
- lerno 4y agoVariants... that's a bug in the documentation. Thanks for finding it! I agree about strings. There are basically two strings we use: the string builder and the "string data" which is any sort of slice of bytes that can be interpreted as a string. But starting from there are a lot of different ways one can name and implement them. I'm doing some experiments and before those are finished I don't have a strong opinion. One thing to note though is that C3 does not have RAII or move semantics and so the C++ std::string or similar isn't even an option.
- mst 4y agoIf I have that set of semantics I'll use nim compiled to C with ORC attached. You're aiming for something different, and I appreciate the idea even if I never end up using it. In the unlikely case you haven't already read it about the only time I got C to do what I wanted without just segfaulting everywhere (my mistakes) was using djb's substdio.
- lerno 4y agoI actually tried to look for substdio but I failed to get anything good by googling (just that qmail uses it)
- synergy20 4y agoAwesome stuff. It seems like a C+ instead of C++ to me. I wish it has class/object/ctor/dtor/RAII though, no need for exception. OOD in C using struct and function pointers are doable but is a bit cumbersome. I don't even need runtime overloading or virtual inheritance or any fancy/advanced features of c++, just a better way to organize code in an OOD style is enough, something like Javascript's class sugar syntax to its prototype object syntax(here will be c++ style to struct+function-pointer-style).
- lwswl 4y agomultiple function pointers in a struct is inefficient memory-wise, I think you'd be much better off with a single pointer to a lookup table that can be shared by all instances. Its even more cumbersome, but its what you pay if you want efficiency in c.
- mhh__ 4y agoIn my experience of using D at work alongside people who barely know how to program (i.e. smart but not culturally a Dev), and people who just didn't know D: if you have smart devs simplicity doesn't matter very much. If anything it's easier to teach a concept than wait for a programmer to be able to see through a wall of messy simplicity.
- msla 4y agoHey, I like any language with this kind of goto: http://www.c3-lang.org/statements/#nextcase-and-labelled-nextcase http://www.c3-lang.org/statements/#nextcase-and-labelled-nex... > It's also possible to use nextcase with an expression, to jump to an arbitrary case: switch (i) { case 1: doSomething(); nextcase 3; // Jump to case 3 case 2: doSomethingElse(); case 3: nextcase rand(); // Jump to random case default: libc::printf("Ended\n"); } > Which can be used as structured goto when creating state machines.
- sakras 4y agoHoly crap I’ve been wanting to make basically this exact language for a while now: C with modules and defer! I am wondering how strings are represented (I dislike the sentinel value scheme we have now), and what the library/locale situation is like (hoping it just says everything is UTF8). Awesome that someone went and did it for me! The one thing I don’t like is the fn declaration, but reading other comments it makes sense why it’s there and I’m sure I’ll get used to it.
- lerno 4y agoThere isn't much of a reason to use anything beyond UTF8 today except for interfacing with code and documents that use UTF16.
- deleted 4y ago[deleted]
- childintime 4y agoA big win C3 explicitly opts out of are integer types named by their size such as u8/u16/u32/.., yet explicitly sizes the types it does have. Another sore point in C: how do expressions involving different integer types work?
- lerno 4y agoDo you wonder about how type promotion rules work in C3? If so then I can explain them as they differ somewhat from C. The reason for not using bitsize numbered types is readability e.g. `for (i8 i = 8; i > 0; i--)`
- pratk 4y agoLooks interesting, thanks! Just curious, any plans for supporting SIMD, similar to gcc's vector extensions ?
- lerno 4y agoVector types are built in, which you can do operations on, e.g. float[<4>] y = { 1, 2, 3, 4 }; float[<4>] z = { 2, 2, 2, -100 }; float[<4>] w = y + z; It's not completely fleshed out with builtins yet, but will be.
- sylware 4y agoJust take C, remove the integer promotion and implicit casts (it means casting constant literals, yes), except for void* ofc. Remove those horrible __thread, generic, typedef (use the preprocessor for function types?), restrict, all optimization "hints", etc keywords. Finally standardize properly the preprocessor macro with a variadic number of arguments... Split completely the syntax definition from the the runtime lib (the main() function, etc). It seems there is more to remove from C than to add.
- euclaise 4y agoI think we do need a language like this, and that Zig/Odin/Myrddin/etc have the issue of having a syntax too different from C (Zig being the least offending; Hare is okay but perhaps a bit too minimal). This is great. Are there plans for a package manager? What's the target stdlib size? That said, I'm not a big fan of the naming. It irrationally feels kinda hard to justify using a language called "C3" (and I also irrationally like cute names and mascots like Hare has).
- lerno 4y agoThere is a dependency relationship defined in C3 for libraries, but there is not (yet anyway) any package manager to actually pull those from a remote repository. It just ensures they are present when using it. The standard library is far from done, but I imagine it having a common core, supported by all targets. Then a common set of modules that are available on most but not necessarily ALL targets. Outside of that there are “standard library ‘extras’” which will only be of use for certain programs. Beyond that there’s the vendor collection of officially supported external vendor libraries that are not part of the standard library.
- lerno 4y agoRegarding the name, I am not in love with it, but people like my other names even less… Suggestions are always appreciated.
- euclaise 4y agoI've been thinking of "Xi" or something along those lines. You might also consider something mythological like Odin and Myrddin, or something cute like Hare.