12 ms·
The C3 Programming Language
- xprnio 10mo agoTsoding did a bunch of livestreams using C3. Over 30h worth if anyone’s interested. https://youtube.com/playlist?list=PLpM-Dvs8t0VYwdrsI_O-7wpo-_MMIdeX8 https://youtube.com/playlist?list=PLpM-Dvs8t0VYwdrsI_O-7wpo-...
- sea-gold 10mo agoPrevious discussions: July 2025 (159 comments, 143 points): https://news.ycombinator.com/item?id=44532527 https://news.ycombinator.com/item?id=44532527
- theflyinghorse 10mo agoI keep thinking about perhaps LLMs would make writing code in these lower-level-but-far-better-performing languages in vogue. Why have claude generate a python service when you could write a rust or C3 service with compiler doing a lot of heavy lifting around memory bugs?
- klysm 10mo agoBecause there’s more python on the internet to interpolate from. LLMs are not equally good at all languages
- venuur 10mo agoThat’s been my experience. LLMs excel at languages that are popular. JavaScript and Python are two great examples.
- yencabulator 10mo agoYou can throw Claude at a completely private Rust code base with very specific niche requirements and conventions that are not otherwise common in Rust and it will demonstrate a remarkably strong ability to explain it and program according to the local idioms. I think your statement is based on liking a popular language, not on evidence..
- all2 10mo agoI find that having a code-base properly scaffolded really, really helps a model handle implementing new features or performing bug-fixes. There's this grey area between greenfield and established that I hit every time I try to take a new project to a more stable state. I'm still trying to sort out how to get through that grey area.
- yencabulator 10mo agoI had Claude nearly one-shot (well, sequence-of-oneshots) a fairly complex multi-language file pretty-printer, but only after giving it a very specific 150-line TODO file with examples of correct results, so I think pure greenfield is very achievable if you steer it well enough. I did have to really focus on writing the tasks to be such that there wasn't much room for going off the rails, thought about their ordering, etc; it was pretty far from vibecoding, produced a strict data-driven test suite, etc. But ultimately, I agree with you, in most projects, having enough existing style, arranged in a fairly specific way, for Claude to imitate makes results a lot better. Or at least, until you get to that "good-looking codebase", you have to steer it a lot more explicitly, to the level of telling it what function signatures to use, what files to edit, etc. Currently on another project, I've had Claude make ~10 development spikes on specific ~5 high-uncertainty features on separate branches, without ever telling it what the main project structure really is. Some of the spikes implement the same functionality with e.g. different libraries, as I'm exploring my options (ML inference as a library is still a shitshow). I think that approach has some whiff of "future of programming" to it. Previously I would have spent more effort studying the frameworks up front and committed to a choice harder, now it's "let's see if this is good enough".
- notimetorelax 10mo agoHaving worked with rust in the past couple years, I can say that it hands down much better fit for LLMs than Python thanks to its explicitness and type information. This provides a lot of context for LLM to incrementally grow the codebase. You still have to watch it, of course. But the experience is very pleasant.
- dotancohen 10mo ago> Why have claude generate a python service when you could write a rust or C3 service with compiler doing a lot of heavy lifting around memory bugs? The architecture of my current project is actually a Python/Qt application which is a thin wrapper around an LLM generated Rust application. I go over almost every line of the LLM generated Rust myself, but that machine is far more skilled at generating quality Rust than I currently am. But I am using this as an opportunity to learn.
- all2 10mo ago> that machine is far more skilled at generating quality Rust than I currently am. But I am using this as an opportunity to learn. I'm currently doing this with golang. It is not that bad of an experience. LLMs do struggle with concurrency, though. My current project has proved to be pretty challenging for LLMs to chew through.
- HighGoldstein 10mo agoIt seems cynically fitting that the future we're getting and deserve is one where we've automated the creation of memory bugs with AI.
- sonnig 10mo agoI think the same. It sounds quite more practical to have LLMs code in languages whose compilers provide as much compile-time guardrails as possible (Rust, Haskell?). Ironically in some ways this applies to humans writing code as well, but there you run into the (IMO very small) problem of having to write a bit more code than with more dynamic languages.
- gitaarik 10mo agoYou still want to be able to easily review the LLM generated code. At least I want to.
- antfarm 10mo agoThis is what a website for a programming language should look like.
- GaryBluto 10mo agoI'd argue the opposite. My first thought once the page had loaded was that it looked childish and amateurish, the addition of a Discord chat link in the site navigation only reinforcing that perspective.
- CyberDildonics 10mo agoNothing childish about putting the important stuff up front and a link to contact the people who made it.
- netbioserror 10mo agoThis is neat and I wish C3 well. But using Nim has shown me the light on maybe the most important innovation I've seen in a native-compiled systems language: Everything, even heap-allocated data, having value semantics by default. In Nim, strings and seqs exist on the heap, but are managed by simple value-semantic wrappers on the stack, where the pointer's lifetime is easy to statically analyze. Moves and destroys can be automatic by default. All string ops return string, there are no special derivative types. Seq ops return seq, there are no special derivative types. Do you pay the price of the occasional copy? Yes. But there are opt-in trapdoors to allocate RC- or manually-managed strings and seqs. Otherwise, the default mode of interacting with heap data is an absolute breeze. For the life of me, I don't know why other languages haven't leaned harder into such a transformative feature.
- sirwhinesalot 10mo agoNOTE: I'm a fan of value semantics, mostly devil's advocate here. Those implicit copies have downsides that make them a bad fit for various reasons. Swift doesn't enforce value semantics, but most types in the standard library do follow them (even dictionaries and such), and those types go out of their way to use copy-on-write to try and avoid unnecessary copying as much as possible. Even with that optimization there are too many implicit copies! (it could be argued the copy-on-write makes it worse since it makes it harder to predict when they happen). Implicit copies of very large datastructures are almost always unwanted, effectively a bug, and having the compiler check this (as in Rust or a C++ type without a copy constructor) can help detect said bugs. It's not all that dissimilar to NULL checking. NULL checking requires lots of extra annoying machinery but it avoids so many bugs it is worthwhile doing. So you have to have a plan on how to avoid unnecessary copying. "Move-only" types is one way, but then the question is which types do you make move-only? Copying a small vector is usually fine, but a huge one probably not. You have to make the decision for each heap-allocated type if you want it move-only or implicitly copyable (with the caveats above) which is not trivial. You can also add "view" types like slices, but now you need to worry about tracking lifetimes. For these new C alternative languages, implicit heap copies are a big nono. They have very few implicit calls. There are no destructors, allocators are explicit. Implicit copies could be supported with a default temp allocator that follows a stack discipline, but now you are imposing a specific structure to the temp allocator. It's not something that can just be added to any language.
- TZubiri 10mo agoIf I had a dollar for every C successor...
- maximgeorge 10mo ago[dead]
- gritzko 10mo agoI wonder, at which point it is worth it to make a language? I personally implemented generics, slices and error propagation in C… that takes some work, but doable. Obviously, C stdlib goes to the trash bin, but there is not much value in it anyway. Not much code, and very obsolete. Meanwhile, a compiler is an enormously complicated story. I personally never ever want to write a compiler, cause I already had more fun than I ever wanted working with distributed systems. While idiomatic C was not the way forward, my choice was a C dialect and Go for higher-level things. How can we estimate these things? Or let's have fun, yolo?
- contificate 10mo ago> Meanwhile, a compiler is an enormously complicated story. I don't intend to downplay the effort involved in creating a large project, but it's evident to me that there's a class of "better C" languages for which LLVM is very well suited. On purely recreational grounds, one can get something small off the ground in an afternoon with LLVM. It's very enjoyable and has a low barrier to entry, really.
- fuzztester 10mo ago>On purely recreational grounds, one can get something small off the ground in an afternoon with LLVM. It's very enjoyable and has a low barrier to entry, really. Is there something analogous for those wanting to create language interpreters, not compilers? And preferably for interpreters one wants to develop in Python? Doesn't have to literally just an afternoon, it could be even a few weeks, but something that will ease the task for PL newbies? The tasks of lexing and parsing, I mean.
- fredrikholm 10mo agohttps://craftinginterpreters.com/introduction.html https://craftinginterpreters.com/introduction.html AST interpreter in Java from scratch, followed by the same language in a tight bytecode VM in C. Great book; very good introduction to the subject.
- 10mo ago
- impoppy 10mo agoI haven’t tried C3 myself, but I happened to interact a lot with Christopher Lerno, Ginger Bill and multiple Zig maintainers before. Was great to learn that C3, Odin and Zig weren’t competing with each other but instead learn from each other and discuss various trade-offs they made when designing their languages. Generally was a very pleasant experience to learn from them on how and why they implemented building differently or what itch they were scratching when choosing or refusing to implement certain features.
- ivanjermakov 10mo ago> weren’t competing with each other but instead learn from each other What is the difference? Using polite words to communicate?
- edvinbesic 10mo agoYou catch more flies with honey than with vinegar
- ibuildbots 10mo agowhy would I want to attract bugs? Vinegar keeps them away from me
- card_zero 10mo agoIt isn't even true. https://xkcd.com/357/ https://xkcd.com/357/ "Head over heels" is another weird idiom. I'm so in love, I'm standing in a normal orientation.
- atombender 10mo ago"Head over heels" is actually a corruption of "heels over head". It's one of those corruptions which flips the meaning (ironically, in this case!) on its head, or just becomes meaningless over time as it's reinterpreted (like "the exception that proves the rule" or "begs the question").
- Fiveplus 10mo agoI've been following C3 for sometime now, and I really appreciate the discipline in the design philosophy here. Neither does it force a new memory model on you, nor does it try to be C++. The killer feature for me is the full ABI compatibility. The fact that I no longer have to write bindings and can just mix C3 files into my existing C build system reduces the friction to near zero. Kudos to the maintainer for sticking to the evolution, not revolution vision. If you are looking for a weekend language to learn that doesn't require resetting your brain but feels more modern than C99, I highly recommend giving this a shot. Great work by the team.
- astrange 10mo agoIs full ABI compatibility important? I'm having a hard time seeing why. I mean… C isn't even an unsafe language. It's just that C implementations and ABIs are unsafe. Some fat pointers, less insanely unsafe varargs implementations, UBSan on by default, MTE… soon you're doing pretty well! (Exceptions apply.)
- flohofwoe 10mo agoHow would you integrate C3 with other programming languages (not just C), or even talk to operating systems if you don't implement a common ABI? And the various system ABIs supported by C compilers are the defacto standards for that (contrary to popular belief there is no such thing as a "C ABI" - those ABIs are commonly defined by OS and CPU vendors, C compilers need to implement those ABIs just like any other compiler toolchain if they want to talk to operating system interfaces or call into libraries compiled with different compilers from different languages).
- astrange 10mo ago> How would you integrate C3 with other programming languages (not just C) That's the job of an FFI. The internal ABI of most languages isn't anything like their FFI, eg any garbage collected language can't use the OS "C" ABI. Most operating systems don't use the same ABI for kernel syscalls and userland libraries either. (Darwin is an exception where you do have to link a library instead of making syscalls yourself.) > contrary to popular belief there is no such thing as a "C ABI" It is a "C ABI" if it has eg null-terminated strings and varargs with no way to do bounds checking.
- bofia 10mo agoDoes this compile to Rust with an LLVM?
- Alifatisk 10mo agoNo, C3 does not compile to Rust or use Rust in its compilation process.
- givemeethekeys 10mo agoThis looks like Zig. What problems does this solve that Zig doesn't? Potato - potaato?
- ogogmad 10mo agoZig doesn't have operator overloading. This does.
- Alifatisk 10mo agoLooks can be deceiving. C3 provides a module system for cleaner code organization across files, unlike Zig where files act as modules with nesting limitations. C3 offers first class lambdas and dynamic interfaces for flexible runtime polymorphism without Zigs struct based workarounds. C3s operator overloading enables intuitive math types like vectors, which Zig avoids to prevent hidden control flow.
- cardanome 10mo agoC3 is more comparable to Odin or the Better C mode in D in that it tries to be a pragmatic evolution not revolution of C. Here is a comparison to Zig in terms of features: https://c3-lang.org/faq/compare-languages/#zig https://c3-lang.org/faq/compare-languages/#zig And yes, they are all system programming languages with a similar level of abstraction that are suited for similar problem. It is good to have choice. It is like asking what do you need Ruby for when you have Python.
- deleted 10mo ago[deleted]
- cb321 10mo agoI see from `test/test_suite/compile_time_introspection/paramsof.c3t` that there is a way to get names & types of function parameters [1]. The language also seems to support default values { e.g. `int foo(int a, int b = 2) {...}` } and even call with keyword arguments/named parameters [2], but I couldn't find any `defaultof` or similar thing in the code. Does anyone know if this is just an oversight / temporary omission? [1] https://github.com/c3lang/c3c/blob/master/test/test_suite/compile_time_introspection/paramsof.c3t https://github.com/c3lang/c3c/blob/master/test/test_suite/co... [2] https://c3-lang.org/language-fundamentals/functions/ https://c3-lang.org/language-fundamentals/functions/
- Windeycastle 10mo agoI don't think it is available no, and it's the first time I heard about such an idea. Thinking on it, this would allow such cursed code (love that :D). I'll put it up for discussion in the Discord as I'm interested in hearing whether `.defaultof` is a good idea or not.
- cb321 10mo agoOne application of such a feature would be something like a "cligen.c3" (like the Nim https://github.com/c-blake/cligen https://github.com/c-blake/cligen or its /python/cg.py port or etc.). Mostly it just seems a more complete signature extraction, though. Any other kind of documentation system might benefit.
- epage 10mo agoI think the switch statement design is a foot gun: defaults to fall-through when empty and break when there is a body. https://c3-lang.org/language-overview/examples/#enum-and-switch https://c3-lang.org/language-overview/examples/#enum-and-swi...
- gkbrk 10mo agoThis feels very natural though, in a "principle of least surprise" kinda way. This is what you'd expect a properly designed switch statement to do.
- HarHarVeryFunny 10mo agoLeast surprise to who? Are there any other mainstream languages that behave this way? I think consistency is the best correlate of least surprise, so having case statements that sometimes fall though, sometimes not, seems awful.
- SoKamil 10mo agoSwift
- trinix912 10mo agoSadly many of us are so used to the C fall-through behavior that this would be quite a surprise. Personally, I'd rather see a different syntax switch (perhaps something like the Java pattern switch) or no switch at all than one that looks the same as in all C-style languages but works just slightly differently.
- epage 10mo agoIt reads naturally but I can see people getting tripped up writing this. Worse for changing existing code. Refactor in a way that removes a body? Likely forget to add a breake
- riazrizvi 10mo agoIf I aimed and shot a gun at my foot and a bullet didn’t go through it, I would trash the gun.
- jksmith 10mo agoWindows is a horse that is becoming less and less rideable. Be great to get this to build on ReactOS as just a hobby or side effort.
- chuckadams 10mo agoI was expecting something very bare-bones, but was pleasantly surprised to see a rich list of new and useful features. And maybe it's not the deepest of things to highlight, but what really made me giggle is how C3's analogue to exceptions are called Excuses.
- throwfaraway135 10mo agoThe GitHub project has more details: https://github.com/c3lang/c3c https://github.com/c3lang/c3c Some ways C3 differs from C: - No mandatory header files - New semantic macro system - Module-based namespacing - Slices - Operator overloading - Compile-time reflection - Enhanced compile-time execution - Generics via generic modules - "Result"-based zero-overhead error handling - Defer - Value methods - Associated enum data - No preprocessor - Less undefined behavior, with added runtime checks in "safe" mode - Limited operator overloading (to enable userland dynamic arrays) - Optional pre- and post-conditions
- sprash 10mo agoSo far so good. The feature set is bit random though. Things i personally miss is function overloading, default values in parameters and tuple returns.
- cardanome 10mo ago> default values in parameters C3 has you covered https://c3-lang.org/language-fundamentals/functions/#function-arguments https://c3-lang.org/language-fundamentals/functions/#functio... It also has operator overloading and methods which you could use in place of function overloading I guess.
- WhereIsTheTruth 10mo agoWe have solved the better C issue, but nobody seems keen on solving the better compiler issue Why do we still have to recompile the whole program everytime we make a change, the only project i am aware of who wants to tackle this is Zig with binary patching, and that's imo where we should focus our effort on.. C3 does look interesting tho, the idea of ABI compatibility with C is pretty ingenious, you get to tap into C's ecosystem for free
- flohofwoe 10mo ago> Why do we still have to recompile the whole program everytime we make a change That problem was solved decades ago via object files and linkers. Zig needs a different approach because its language features depend on compiling the entire source code as a single compilation unit, but I don't think that C3 has that same "restriction" (not sure though).
- deleted 10mo ago[deleted]
- HarHarVeryFunny 10mo ago> Why do we still have to recompile the whole program everytime we make a change Are you talking about compiling, or linking, or both? GNU ld has supported incremental linking for ages, and make systems only recompile things based on file level dependencies. I guess recompilation could perhaps be smarter based on what changed or was added/deleted to a module definition (e.g C header file), but this would seem difficult to get right. Maybe you just add a new function to a module, so no need to recompile other modules that use it, right? Except what if there is now a name clash and they would fail if recompiled?
- sirwhinesalot 10mo agoC3 doesn't have a recompile everything model, in fact it's pretty much designed around supporting separate compilation and dynamic linking (unlike Zig and Odin), it even supports Objective-C style dynamic calls.
- 10mo ago
- rixed 10mo agoJust browsed the doc to get the answers to two burning questions, which I will dump here in case it saves some time to others: - uses LLVM (so: as portable as LLVM) - sadly, does not support tagged enums Apart from that it adds a few very desirable things, such as introspection and macros.
- deleted 10mo ago[deleted]
- flohofwoe 10mo agoIMHO the downsides of tagged unions (e.g. what Rust confusingly calls "enums") are big enough that they should only be used rarely if at all in a systems programming language since they're shoehoerning a dynamic type system concept back into an otherwise statically typed language. A tagged union always needs at least as much memory as the biggest type, but even worse, they nudge the programmer towards 'any-types', which basically moves the type checking from compile-time to run-time, but then why use a statically typed language at all? And even if they are useful in some rare situations, are the advantages big enough to justify wasting 'syntax surface' instead of rolling your own tagged unions when needed?
- sph 10mo agoTagged enums != any type (i.e. runtime casting) Tagged enums are everywhere. I am writing a micro kernel in C and how I wish I had tagged enums instead of writing the same boilerplate of enum foo_type { FOO_POINTER, FOO_INT, FOO_FLOAT, }; struct foo { enum foo_type type; union { void *val_pointer; int val_int; float val_float; }; };
- flohofwoe 10mo ago> ...runtime casting... ...what else is a select on a tagged union than 'runtime casting' though. You have a single 'sum type' which you don't know what concrete type it actually is at runtime until you look at the tag and 'cast' to the concrete type associated with the tag. The fact that some languages have syntax sugar for the selection doesn't make the runtime overhead magically disappear.
- bigbadfeline 10mo ago[flagged]
- gdsdfe 10mo agoHonestly if your programming language does not compile to wasm I don't care for it. That's my new rule.
- loeg 10mo agoThey named Result (or Expected) Optional? No, no, "optional" means "T or empty." Not "T or E." https://c3-lang.org/language-fundamentals/functions/#functions-and-optional-returns https://c3-lang.org/language-fundamentals/functions/#functio...
- fwip 10mo agoI share your distaste for arbitrarily renaming concepts. However, I think if you only have one of the two in the language, Optional is the clearer name. A result is already the informal name of the outcome or return value of every regular operation or function call, whereas an Optional is clearly not a regular thing. I also think, from a pragmatic systems-design point of view, it might make sense to only support the Either/Result pattern. It's not too much boilerplate to add a `faultdef KeyNotInMap`, and then it's clear to the consumer why a real answer was not returned.
- loeg 10mo agoIt's not just arbitrary renaming (Rust Result vs C++ Expected is fine) -- it's choosing a conflicting name for an extremely common abstract data type. If they wanted to call it "Elephant," great, but Optional is a well-known concept and Result/Expected isn't the same thing. (I don't really object to the idea of skipping a real Optional<T> type in a language in favor of just Result<T, ()>.)
- jeremyjh 10mo agoI would guess the two languages have little overlap in who they appeal to, and this is a bit closer to C semantics which is their north star. In C for functions that don’t return a value you often return a null for success or a return code that is a defined error code, which is exactly how Optional works in C3 (but you don’t write the word Optional in the function head). If you need to return a value in C you often pass an out param pointer and then still return either null or a defined error value. The improvement is you just write ‘type?’ for the return type to enable this and you don’t need an out param, but you still need the define (faultdef).
- zephen 10mo ago" the C-like for programmers who like C." Sounds intriguing. But then, the first thing I noticed in their example is a double-colon scope operator. I understand that it's part of the culture (and Rust, C#, and many other languages), but I find the syntax itself ugly. I dunno. Maybe I have the visual equivalent of misophonia, in addition to the auditory version, but :: and x << y << z << whatever and things like that just grate. I like C. But I abhor C++ with a passion, partly because of what, to me, is jarring syntax. A lot of languages have subsequently adopted this sort of syntax, but it really didn't have that much thought put into it at the beginning, other than that Stroustrup went out of his way to use different symbols for different kinds of hierarchies, because some people were confused. Source: https://medium.com/@alexander.michaud/the-double-colon-operator-75543555fc08 https://medium.com/@alexander.michaud/the-double-colon-opera... Think about that. The designer of a language that is practically focused on polymorphism went out of his way to _not_ overload the '.' operator for two things that are about as close semantically as things ever get (hierarchical relationships), simply because some of his customers found that overloading to be confusing. (And yet, '<<' is used for completely disparate things in the same language, but, of course, apparently, that is not at all confusing.) I saw in another comment here just now that one of the differentiators between zig and C3 is that C3 allows operator overloading. Honestly, that's in C3's favor (in my book), so why don't they start by overloading '.' and get rid of '::' ?
- nathan_compton 10mo agoAs a long time lisper I can't stand how much syntax languages have and I think of excess syntax as a sign of a childish mind, but what can you do?
- zephen 10mo agoAs I get older, I realize that everybody's sweet spot is a little different. Lisp and APL both have their adherents. I personally find a bit more syntax than lisp to be nice. Occasionally I long for the homoiconicity of lisp; otoh, many of the arguments for it fall flat with me. For example, DSLs -- yeah, no, it's hard enough to get semi-technical people to use DSLs to start with, never mind lisp-like ones.
- stacktraceyo 10mo agoMy university had us program in c++ with resolve. Which looks very similar to the contract stuff here.
- maxloh 10mo agoGoogle is making a similar attempt with C++ called Carbon Language: https://github.com/carbon-language/carbon-lang https://github.com/carbon-language/carbon-lang
- Windeycastle 10mo agoC3 is more targeting C instead of C++. I'm interested to see where both C3 and Carbon will be in a few years/decades.
- ulimn 10mo agoI am not a C programmer but I always find these low level languages intriguing. I am wondering though: when does one pick C3 for a task/problem?
- Windeycastle 10mo agoC3 is pretty much aimed at those who would use C for a task but prefer something a bit more modern. I find it more fun to program in compared to plain C, and it's definitely more simple than reaching for C++.
- Fraterkes 10mo agoDumb question about contracts: I was reading the docs (https://c3-lang.org/language-common/contracts/ https://c3-lang.org/language-common/contracts/) and this jumped out "Contracts are optional pre- and post-condition checks that the compiler may use for static analysis, runtime checks and optimization. Note that conforming C3 compilers are not obliged to use pre- and post-conditions at all. However, violating either pre- or post-conditions is unspecified behaviour, and a compiler may optimize code as if they are always true – even if a potential bug may cause them to be violated. In safe mode, pre- and post-conditions are checked using runtime asserts." So I'm probably missing something, but it reads to me like you're adding checks to your code, except there's no guarantee that they will run and whether it's at compile or runtime. And sometimes instead of catching a mistake, these checks will instead silently introduce undefined behaviour into your program. Isn't that kinda bad? How are you supposed to use this stuff reliably? (otherwise C3 seems really cool!)
- pkulak 10mo agoIt seems to me like a way to standardize what happens all the time anyway. Compilers are always looking for ways to optimize, and that generally means making assumptions. Specifying those assumptions in the code, instead of in flags to the compiler, seems like a win.
- riazrizvi 10mo agoIt’s giving you an expression capability so that you can state your intent, in a standardized way, that other tooling can build off. But it’s recognizing that the degree of enforcement depends on applied context. A big company team might want to enforce them rigidly, but a widely used tool like Visual Studio would not want to prevent code from running, so that folks who are introducing themselves to the paradigm can start to see how it would work, through warnings, while still being able to run code.
- SkiFire13 10mo agoThis is not just expressing intent. The documentation clearly states that it's UB to violate them, so you need to be extra careful when using them.
- logicprog 10mo agoThis seems pretty neat! Still holding out for a language with Go's runtime and compilation and performance characteristics, but language syntax and semantics like Gleam... Maybe one day
- Alifatisk 10mo agoBorgo could be your thing? https://borgo-lang.github.io https://borgo-lang.github.io
- logicprog 10mo agoIt looked really good! But seems kinda dead, and I don't know the Go ecosystem well enough to know if dead things can sort of keep working forever
- bmacho 10mo agoThere is also Dingo https://github.com/MadAppGang/dingo https://github.com/MadAppGang/dingo https://dingolang.com https://dingolang.com (heavy AI project)
- sparky4pro 10mo agoXgo
- pelorat 10mo agoThat would be C# ?
- throwaway17_17 10mo agoI understood the sibling comment recommending Ocaml and to a lesser extent Borgo, but OP is looking for a high level functional programming language based on giving Gleam as the reference point. How does C# fit here. I do think the compilation speed and runtime is at least in the same ballpark, but C#, while a perfectly fine language, is definitely not a functional language in syntax or semantics.
- fatty_patty89 10mo agonot being able to do alias i32 = int; was the biggest turn off for me
- lalitmaganti 10mo agoWas an interesting ready but sad to see that there is nothing special for matching or restructuring tagged unions (beyond the special cased optional type). That's one of the things from Rust I miss the most in my day to day work with C/C++.
- jadbox 10mo agoDoes anyone who has actually used C3, Odin, and Zig talk about how to think of these three?
- sph 10mo agoZig feels too much in flux, has some incredible ideas, but I really don't like it syntactically wise, and I really don't like how the author is so stubbornly in favour of unused-variables-as-errors which I believe it's the worst thing to ever have been invented and drives me up the wall. Documentation was still pretty bad last I checked, and that's the bare minimum before I can seriously adopt a new language. C3 feels like home for C developers, there is a real market for language evolutions rather than revolutions (imagine Typescript). The issue is that pretty much nobody knows about C3, most posts about it never get any traction on HN, and it's hard to choose a language with no mind share for anything more serious than toys. Odin is quite nice, has some hype behind it, deservedly. Feels like a nice improvement over C without completely throwing the baby away with the bathwater; perhaps one negative thing might be that it's so opinionated it feels less of a general purpose language than others (with the main dev focused on graphics, there's a lot of syntax sugar for that use case which feels out of place for anyone that is not writing desktop UI or games). Also, while I agree with the author's choice on not rewriting the compiler itself in Odin, as most other languages do, it doesn't strike much confidence that the author would rather develop in C++ than eat his own dog food. I must admit I don't keep up with alternative languages much any more because I believe the Lindy effect to be a force multiplier, and for serious applications it's better to stick with something that is known to work, despite its shortcomings. You only have a few points you can spend on innovation, and if you're developing a complex application, at the very least you want a rock-solid base to build upon. This is why I'm still sticking with C for very low-level programming. Still, all three languages are worth your time.
- Zambyte 10mo ago> and I really don't like how the author is so stubbornly in favour of unused-variables-as-errors FWIW, they also have a goal to emit as much output as possible, even in the face of compilation errors. They have stated that even syntax errors should have the compiler exit with a non-zero exit code, but still produce an executable that will give you a syntax error at runtime. The point of this being to allow you to iterate quickly, but force things like CI to fail.
- CyberDildonics 10mo agoIt seems great to have something that is C but cleaned up, although clay had all that with templates and move semantics. I think leaving out move semantics and destructors is inexcusable at this point. It is not only fundamental, but doesn't affect things like standard libraries, runtimes, or ABIs.
- lerno 10mo agoBut it of course move semantics and destructors affect all things. If the goal is to call and be callable from C without special constructs, how would you make the C code respect the move semantics and destructors?
- CyberDildonics 10mo agoC++ is already callable from C, you can make functions have any call signature that you want. What is the actual problem?
- reactordev 10mo agoBut can I still write a library in C3 and export the symbols to use in bindings? The only thing stopping me from just going full C the rest of my career is cstrings and dangling pointers to raw memory that isn’t cleaned up when the process ends.
- sureglymop 10mo agoMaybe I misunderstand but if the process ends its entire virtual address space is gone no? Did you mean subprocess or something different?
- reactordev 10mo agoOn some OS’s
- loeg 10mo agoIf it isn't cleaned up by process exit, it's not really a process, is it? Just another co-routine running in the bare metal kernel or whatever.
- CyberDildonics 10mo agoWhich one specifically does ending a process not clean up the memory?
- reactordev 10mo agoAny flat memory rtos. Not everything is *nix. For example microcontrollers or aerospace systems.
- CyberDildonics 10mo agoCan you link to one that has individual virtual memory processes where the memory isn't freed? It sounds like what you're talking about is just leaking memory and processes have nothing to do with it.
- syn-nine 10mo agoI've enjoyed using the C3 language to make some simple games [0] and found it really easy to pick up. The only thing that I got hung up on at first was the temporary memory arenas which I didn't know existed and ultimately really liked. [0] https://github.com/Syn-Nine/c3-mini-games https://github.com/Syn-Nine/c3-mini-games
- fatty_patty89 10mo agoI took a look at your github and saw you implemented the same games in multiple languages, which one did you like the most and why?
- syn-nine 10mo agoTo be honest, my favorite language is my own language Tentacode [0], closely followed by my recent experimental language Gar [1]. Tenta is not publicly released yet, but the source can be downloaded on github [2]. I've been experimenting with making games in a bunch of languages to inform the design of Tenta by seeing how much I can strip away and still successfully, efficiently make a meaningful game. [0] https://tentacode.org/docs/language/basic_types/ https://tentacode.org/docs/language/basic_types/ [1] https://github.com/Syn-Nine/gar-lang https://github.com/Syn-Nine/gar-lang [2] https://github.com/Syn-Nine/tentacode/tree/llvm https://github.com/Syn-Nine/tentacode/tree/llvm
- deleted 10mo ago[deleted]
- WalterBright 10mo agoThe front page reads like a D language checklist :-)
- sureglymop 10mo agoCheck it out on the comparisons page: https://c3-lang.org/faq/compare-languages/#d https://c3-lang.org/faq/compare-languages/#d I think they're aware of and like D :)
- WalterBright 10mo agoIt was very nice of the C3 crowd to write that comparison. Thanks for pointing it out to me! I agree that D has gotten a bit complex. We're introducing the notion of "editions" in order dispose of obsolete and unnecessary features.
- gautamcgoel 10mo agoWhat old features would you like to dispose of in a modern redesign of D? What would this new edition look like?
- bluecalm 10mo agoI like the idea of strict improvements to C without taking anything away or making any controversial (as far as language design goes) choices. One thing I am wondering is why new low level languages remove goto (Zig, C3, Nim). I think it's sometimes the cleanest, most readable and most maintainable solution. I get that's rare but especially when you are expressing low level algorithm operating on arrays/blocks/bit streams it can be useful. It's not that you can't express it with "structured" constructs but sometimes it's just not the best way. I get removing backwards goto when you provide alternative constructs for state machines but forward one is useful in other contexts. Is it a purely ideological choice or does it make the compiler simpler/faster?
- lerno 10mo agoIn C3 it's complicated. On one hand the lack of goto means defers are more straightforward, but the biggest problem is that once you have a different way to handle cleanup and you have labelled break/continue, and you have the nextcase to jump to arbitrary cases, there's very little left for goto to do. It is limited to when you want to jump from within an if statement out across some statements and run the remaining code. It saves one level of indentation, but being so rare, it's hard to justify the complexity. I keep going back and see if I find some usecase that could motivate putting goto back in but it so far nothing. The "nextcase" allows C3 to express arbitrary jumps back and forth, although not as straightforward as goto.
- bluecalm 10mo agoLooking at my own code one case I wouldn't get with defer and better switch is avoiding a flag pattern. For example when you iterate over a block and check if positions are 0 (+ do some work) and once you encounter a non zero you jump to a different "non-empty" section but if it's zeros to the end you jump over non-empty to go to end section. Without goto you need to set a flag and add another conditional there. Other than that what you mentioned: flatting the if structure is nice. When you have a few simple cases and then a complicated one and a finishing session at the end it's just cleaner and easier to read with goto. It could be handled with a switch statement but not everything is "switchable" and the way most people write it it's another 2 indentation levels (1 with a convention of not indenting cases but I see C3 docs avoid it). I get it's rare but goto (other than error handling) is rare and I don't think people have a tendency to abuse it. If anything people abuse "structured" construct building an arrow pattern with multiple indentation levels for no good reason.
- oulipo2 10mo agoInteresting! If today I wanted to start a project in a C-like language, I'd have chosen Zig. Would you tell a rough overview of how C3 differs from something like Zig?
- turnsout 10mo agoLooks interesting, but operator overloading is an anti-pattern. Leading with that is like leading with "full support for null pointers."
- mr_00ff00 10mo agoAnyone use zig vs C3? Seems like a lot of overlap, curious about people’s experiences with both
- lerno 10mo agoMaybe these two could be interesting? https://lowbytefox.dev/blog/from-zig-to-c3/ https://lowbytefox.dev/blog/from-zig-to-c3/ https://alloc.dev/2025/05/29/learning_c3 https://alloc.dev/2025/05/29/learning_c3
- maybewhenthesun 10mo agoI see 'fn void main()'. There's probably a good reasson but why the 'fn'? It doesn't really add anything because 'void main()' already communicates it's a function. The main draw of C (to me) is it's terseness and it's avoidance of 'filler' syntax words. I admit I didn't (yet) look much further into it, but this first thing jumped out to me and slightly diminished my desire to look further into C3...
- chippiewill 10mo agoIt's to remove a syntax ambiguity with c-style function declarations https://en.wikipedia.org/wiki/Most_vexing_parse https://en.wikipedia.org/wiki/Most_vexing_parse The syntax ambiguity adds a lot of complexity to the grammar that makes parsing a lot more complicated than it needs to be. Sticking `fn` in front fixes a lot of problems.
- maybewhenthesun 10mo agotnx for the clear explanation
- cardanome 10mo agohttps://c3.handmade.network/blog/p/8886-why_does_c3_use_%2527fn%2527 https://c3.handmade.network/blog/p/8886-why_does_c3_use_%252...
- gigatexal 10mo agoWhy have features but then the compiler doesn’t make programs that enforce it… I’m seeing a lot of this in the docs: “However, just like for const the compiler might not detect whether the annotation is correct or not! This program might compile, but will behave strangely:”
- pelorat 10mo agoDelete "fn" and I might get onboard
- cardanome 10mo agoIt has many usability benefits: https://c3.handmade.network/blog/p/8886-why_does_c3_use_%2527fn%2527 https://c3.handmade.network/blog/p/8886-why_does_c3_use_%252...
- hmry 10mo agoThe price of not requiring fn is mandatory forward declarations, header files, and a slower parser (because then the syntax requires knowing whether every symbol is a type or a function/variable to be parsed correctly) I think that trade-off is absolutely not worth it. I'll take order-independent declarations and fast modules over strictly sticking to C syntax any day.
- chadcmulligan 10mo agoIt's funny seeing the problems with C Niklaus Wirth pointed out originally still trying to be solved. He solved them with pascal and its OO successors, though for some reason it's not cool still. I suppose it has less of the ability to blow your foot off and so isn't a very dangerous way to code, therefore not cool. If any of you younger folk haven't looked at it, I'd suggest having a look, there is Delphi - a cross platform dev environment that addresses all these problems and compiles in less than a second, or there's the free, open source alternative Lazarus. They also compile to mobile platforms and even the raspberry pi (Lazarus) or Arduino. If you like contracts then ADA is the way to go, but I haven't used this for many years, so not sure what is the state of the compilers. [1] https://www.embarcadero.com/products/delphi https://www.embarcadero.com/products/delphi [2] https://www.lazarus-ide.org https://www.lazarus-ide.org
- jll29 10mo agoBoth Pascal and C (and their offspring) are wonderful gifts for us to receive from their designers, and I enjoy writing code in both. > It's funny seeing the problems with C Niklaus Wirth pointed out originally still trying to be solved. He solved them with pascal and its OO successors, though for some reason it's not cool still. Here's Brian Kernighan's view on the shortcomings of Pascal resulting from a practical book project idea: https://www.lysator.liu.se/c/bwk-on-pascal.html https://www.lysator.liu.se/c/bwk-on-pascal.html Not sure to what extent the latest Oberon or Ada have addressed all of these, since I've not kept up with Ada news.
- chadcmulligan 10mo agoIsn't that interesting, I do vaguely recall this from many years ago. These complaints have mostly been addressed a long time ago, the solutions were mostly stolen from C where applicable, I refer to Delphi, but I think Lazarus is the same. These are the dot points from the summary: - Since the size of an array is part of its type, it is not possible to write general-purpose routines, that is, to deal with arrays of different sizes. In particular, string handling is very difficult. There's a TArray<T> type now, it uses generics and can be declared if you like, also lots of other structured types - lists, stacks etc, though the original array type is still available for backwards compatibility. There was also an array of without size to pass as a parameter but TArray is mostly used now. - The lack of static variables, initialization and a way to communicate non-hierarchically combine to destroy the ``locality'' of a program - variables require much more scope than they ought to. Statics are now a thing - The one-pass nature of the language forces procedures and functions to be presented in an unnatural order; the enforced separation of various declarations scatters program components that logically belong together. This can be an issue still, though the one pass is why the compiler is fast. - The lack of separate compilation impedes the development of large programs and makes the use of libraries impossible. Not an issue any more, it has packages and libraries - The order of logical expression evaluation cannot be controlled, which leads to convoluted code and extraneous variables. Not an issue any more it uses the C method - The 'case' statement is emasculated because there is no default clause. Does now, though a case with string alternatives still doesn't exist in Delphi, Lazarus has it. - The standard I/O is defective. There is no sensible provision for dealing with files or program arguments as part of the standard language, and no extension mechanism. Many different sorts of file access - random, binary etc - The language lacks most of the tools needed for assembling large programs, most notably file inclusion. Not true any more, it has packages and include files (though limited), and the macro facility is very limited, nothing like C's but its not really needed, you can have inline functions for the performance boost macros would give you (stolen from C++) - There is no escape. This refers to the type system, you can use casts just like C now Just as a counterpoint C still doesn't have a standard string type. Delphi has generics now like C++, and many of the things that are external libraries in C/C++ are just included. If you really need high performance then C is still better, but what I've done in the past is just rewrite bits in C, though the need for this is very infrequent. If you look at comparable things for Delphi in C++ like Qt's slots and signals for example, the Delphi solution is so much more elegant, and Qt is perhaps the only comparable commercial cross platform library to Delphi's Firemonkey. It's really worth a look, times have changed. There's a reason MS hired away Anders Hejlsberg to architect C# and then typescript.
- deleted 10mo ago[deleted]
- masavik 10mo agoDoes c3 use llvm?
- deleted 10mo ago[deleted]
- Rohansi 10mo agoc3 is as easy as 1,2,3
- shevy-java 10mo agoIts successor will be C4!
- xen0 10mo agoReading through, something small caught me by surprise. https://c3-lang.org/language-common/arrays/#fixed-size-multi-dimensional-arrays https://c3-lang.org/language-common/arrays/#fixed-size-multi... Multi dimensional arrays are not declared in the same way they are accessed; the order of dimensions is reversed. Accessing the multi-dimensional fixed array has inverted array index order to when the array was declared. That is, the last element of 'int[3][10] x = {...}' is accessed with 'x[9][2]'. This seems bizarre to me. What am I missing? Are there other languages that do this?
- sph 10mo agoFile it with the footgun of the two different array slicing syntaxes: https://c3-lang.org/language-common/arrays/#slicing-arrays https://c3-lang.org/language-common/arrays/#slicing-arrays I have already opened a discussion about this with the author, and I must say I agree to disagree that a language needs arr[start..end] (inclusive) as well as arr[start:len] (up to len-1) and if you use the wrong one, you’ve now lost a foot and your memory is corrupted.
- xen0 10mo agoThe closed intervals for slices caught my eye as well, but I simply filed that under 'that's a weird quirk' rather than 'wtf?'. It would require more thinking on my end to change that to either 'this is an acceptable choice' or 'this is a terrible idea'. But the array indices being reversed on declaration? I cannot think of an upside to that at all.
- deleted 10mo ago[deleted]
- lerno 10mo agoPlease consider a variable `List{int}[3] x`, this is an array of 3 List{int} containing List{int}. If we do `x[1]` we will get an element of List{int}, from the middle element in the array. If we then further index this with [5], like `x[1][5]` we will get the 5th element of that list. If we look at `int*`, the dereference will peel off the `*` resulting in `int`. So, the way C3 types are declared is the most inside one is to the left, the outermost to the right. Indexing or dereferencing will peel off the rightmost part. C uses a different way to do this, we place `*` and `[]` not on the type but on the variable, in the order it must be unpacked. So given `int (*foo) x[4]` we first dereference it (from inside) int[4], then index from the right. If we wanted to extract a standalone type from this, we'd have `int(*)[4]` for a pointer to an array of 4 integers. For "left is innermost", the declaration would instead be `int[4]*`. If left-is-innermost we can easily describe a pointer to an array of int pointers (which happens in C3 since arrays don't implicitly decay) int*[4]*. In C that be "int*(*)[4]", which is generally regarded as less easy to read, not the least because you need to think of which of * or [] has priority. That said, I do think that C has a really nice ordering to subscripts, but it was unfortunately not possible to retain it.