C3 Language at 0.7.5: Language tweaks and conveniences
Posted by Nuoji@reddit | programming | View on Reddit | 28 comments
Just released C3 0.7.5! For those unfamiliar, C3 is a systems programming language that takes a different approach than Zig or Odin in the "better C" space.
(What makes C3 different: Evolutionary, not revolutionary, tries to stay close to C while fixing its pain points. You can learn it quickly if you know C. Compile-time introspection and programming without too much complexity. Modern conveniences (slices, error handling, defer, operator overloading) that compile down to what you'd write by hand. Familiar syntax - No need to relearn everything)
This release adds:
- Module aliasing:
alias io = module std::io
- Compile-time ternary:
$debug ??? "verbose" : "quiet"
- Better macro system with optional parameters
- Tons of QoL improvements
SecretTop1337@reddit
The only parts they kept of C are the shitty ones, vague builtin type names (int, long, etc)
Nuoji@reddit (OP)
I am assuming that by ”constraints” you mean contracts. And that your problem with it is that you don’t like the syntax, because it resembles something grouped like block comments, even though they are not comments and comments have their own syntax?
People coming from Rust wanting their i32 syntax is a common complaint. You can set up whatever alias you prefer, but these are the built-in names, similar to how D, C# and Java does it.
SecretTop1337@reddit
I’m not coming from rust, I actively despise rust’s fn, let, types on the right, and symbol soup garbage.
I’m coming from C, where we use stdint extensively.
Nuoji@reddit (OP)
Then just do `alias Int32 = int;` and so on if you prefer it.
uCodeSherpa@reddit
I personally don’t think this is it. There’s a reason languages are shifting to include bit length in the type name on language primitives. Heck, even C programmers in general should be actively avoiding builtin primitives and using a header with specific lengths stated instead.
Having the bit length is easier to look at than aliases. It gives information at a glance. It provides guarantees for now and the language future. You don’t have to “just know” things which makes working in multiple languages far easier.
Personally, I think that new languages not employing easy wins like this are doa.
SecretTop1337@reddit
I agree, my new language is doing the same thing as you suggest, u8/16/32/64 for unsigned, s8/16/32/64 for signed and f16/32/64 for floating point builtins.
Nuoji@reddit (OP)
Note that the fXX scheme breaks on brain floats.
SecretTop1337@reddit
What’s a brain float?
Nuoji@reddit (OP)
There's only a 16 bit version, so you the bfxx scheme for one bit size only.
The argument that it's hard to know the bitwith falls apart when considering C#, D and Java.
The idea that it's somehow inevitably "modern" or "preferable" are just code words for "I am used to this".
Saying it's shorter is also a non-argument. First of all the most commonly used type has the same length: i32 vs int. Secondly, there are plenty of int + bit schemes that doesn't have that and yet they're considered modern, e.g. Swift with Int32.
What iXX vs int schemes boil down to is largely just: "do you prioritize a set of identifier with uniform naming or do you value continuity with old type names?"
(That's given that readability is the same, with C style "for" the "i32" scheme is less readable than "int", but a similar bit-named scheme can be introduced with s32/u32 which doesn't have that problem)
SecretTop1337@reddit
Yeah, I’m used to stdint.
Say and do what you want, I ain’t touching a long word 🤷
Nuoji@reddit (OP)
Gray beard or long experience, same thing.
Yeah, but it didn't get traction until around 2010 or so. But it's beside the point. You won't see people coming from C#, Java or Kotlin confused at the least. And old pre-stdint codebases often define their own
Long
type (uppercase deliberate) as a fixed 64 bit int.So it's not novel by far. But I know the
iXX
scheme is popular these days. It was popularized by Rust and later also adopted by Zig (although the Zig type names likely comes by way of LLVM IR rather than from Rust).It's the new hot popular thing, which is a good reason to avoid it.
Nuoji@reddit (OP)
We disagree on this then.
wFXx@reddit
out of curiosity, whats the reason for having a special ternary operator - ??? - for comptime; I saw that this is consistent with other operators, but wouldn't the $ sigil in itself wouldn't be enough to "promote" the expression to compile time?
Nuoji@reddit (OP)
It would be locally ambiguous, and that's what I'm trying to avoid. An example:
Seeing this code, it is unclear whether
a
might be evaluated or not. Is the intent thata
is only available ifABC
is true? Or is the value ofABC
irrelevant and it compiles regardless of value. If we have a compile time ternary that was the same as the normal ternary, then we would not know.However, if we instead maintain them as distinct, then we get:
Here we know that
???
is deliberately picked over?
to prevent evaluation of one of the ternary's legs. Presumably becausea
only exists conditionally. And vice-versa, if we pickABC ? a : 1
we know for sure thata
is a valid global symbol regardless of value of ABC.C3 tries to be exceedingly explicit about what's compile time and what isn't.
There is the other type of design which is the flip side, where runtime and compile time are indistinguishable from each other. This can also work.
What I don't like is the ambivalent positioning in the middle, where sometimes compile time looks like runtime and sometimes not, so you're not sure of where you stand but you need to know.
matthieum@reddit
I'm confused, why would
a
be evaluated ifABC
is false? The point of the ternary operator is for the not-taken branch not to be evaluated.Or does
???
allow the expressiona
not to type-check if not necessary?Nuoji@reddit (OP)
Exactly, with ternary both branches are typechecked, but only one evaluated at runtime. With ??? only one is typechecked and kept to runtime.
wFXx@reddit
Really appreciate the answer;
I'm gonna read your docs to understand better how this works - but I feel like it would make sense to have one symbol/sigil that marks an entire expression as comptime - preferably at the start of it - than having more than one symbol for "the same behavior";
eg.:
Thank you and the contributors for the work on c3 though, really like it so far
Nuoji@reddit (OP)
It's something I've thought of, but this adds odd complexity to the language: having something that turns on/off the "compiletimeness" of the code. It's a better model when the language itself is completely runtime/compiletime agnostic I think. At least I haven't found a good model for using that solution.
ArtOfWarfare@reddit
Just from this post, you’re not following sem-var (all code valid in 0.7.5 should also be valid in 0.7.0, because there should only be bug fixes between the two) and I can also pretty easily conclude you’re not staying close to C at all. My familiarity with C is not going to help me understand any code utilizing the new features you’re adding in 0.7.5, nevermind all the releases that came before this one.
Nuoji@reddit (OP)
That's an unreasonable assumption. 0.7.0 code is valid with the 0.7.5 compiler, but not vice versa which is reasonable. The versioning scheme is explained in several places:
Since it has not reached 1.0 yet, this is how it has to work. To recover "semver", you can think of 0.7.0 as 7.0, 0.7.5 as 7.5 etc.
A primer for C programmers can be found here: https://c3-lang.org/language-overview/primer/
FullPoet@reddit
I think its a troll account.
FullPoet@reddit
r/programming and insane comments, name a better duo.
spirit-of-CDU-lol@reddit
Semver doesn't have any stability guarantees below version 1.0.0 actually
IllAdministration865@reddit
Why C3 instead of C2? Is C++ considered "C2"?
Nuoji@reddit (OP)
I started out contributing to the C2 language. When that development was stalled, C3 was born. So that’s where the name’s from.
JayBoingBoing@reddit
Does it offer any safety features like Zig or Rust (I know they’re completely different)?
Nuoji@reddit (OP)
Yes, a few but more in the sense that Zig has safety than Rust. So it has all the standard checks in safe mode: array boundary checks, null pointer, etc similar to Zig. It also has thread and address sanitizers available out of the box.
On top of that it has *contracts* that will be checked at compile time and runtime. The former checks depends on static analysis, and so will improve over time.
JayBoingBoing@reddit
Thanks you, sounds intriguing.