Live data from Hacker News

The C3 Programming Language

c3-lang.org

251–260 of 270 posts

Re: The C3 Programming Language

#251
post #241

Reading through, something small caught me by surprise. 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]'.…

Please 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.

Re: The C3 Programming Language

#253
post #136

Earlier quoted context omitted.

In 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…

Looking 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. Othe…

That's the kind of thing I was thinking about. You can solve that with a switch in C3, but it's not as nice. However, this accounts for no more than 1% of all my goto uses (from a quick inspection), which is too little to build a feature from (discipline is needed to prevent a language from ballooning, it's hard to say no). I am looking for some use for it that can redeem its inclusion.

Re: The C3 Programming Language

#254

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. 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…

I would argue that Go is the closest spiritual descendant of Wirth's languages. If you changed braces into BEGIN/END and so on, it would look a ton like Oberon or Modula 2/3. It adds features (goroutines, channels, slices), changes some (modules become packages), the generics are a little different, and it eschews some of Wirth's pragmatic type safety ideas (like range types). It even has ":=" for assignment. The gen…

The part of Delphi that is interesting, and isn't really mentioned much for some reason, is the component library - VCL (windows only) and Firemonkey (Cross platform). Like the language does what's needed, garbage collection would be nice, and is on iOS, but the really nice part is the ability to make things by dragging and dropping visual and non visual components, and making your own components in the same language.

Re: The C3 Programming Language

#255

Earlier quoted context omitted.

Is 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.)

How 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 compi…

> 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.

Re: The C3 Programming Language

#256
post #253

Earlier quoted context omitted.

Looking 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. Othe…

That's the kind of thing I was thinking about. You can solve that with a switch in C3, but it's not as nice. However, this accounts for no more than 1% of all my goto uses (from a quick inspection), which is too little to build a feature from (discipline is needed to prevent a language from ballooning, it's hard to say no). I am looking for some use for it that can redeem its inclusion.

I agree it's very rare. I have this goto. A few that go to common return block that can be handled by other means and the rest is either error handling or things handled by labelled break or switch.

I mean we know you can program without it and defer/labelled switch and labelled break/continue cover 99%+ of use cases of it. I am still not convinced those are in fact easier to read but I get it's a reasonable design choice to make.

Re: The C3 Programming Language

#257

Earlier quoted context omitted.

> The developer has the choice The developer has the choice between fast or safe. They don't have a choice for checking pre/post conditions, or at least avoiding UB when they are broken, while getting the other benefits of the "fast" mode. And all in all the biggest issue is that these can be misinterpreted as a safety feature, while they actually add more possibilities for UB!

Well, the C3 developer could add more fine grained control if people need it... I don't really see what's your problem. It's not so much different than disabling asserts in production. Some people don't do that, because they rather crash than walking into invalid program state - and that's fine too. It largely depends on the project in question.

> It's not so much different than disabling asserts in production.

Disabling asserts would be equivalent to not having them at all, while this feature introduces _new_ UB. In "fast" mode it's equivalent to using C's `__builtin_assume` or Rust's `std::hint::assert_unchecked`, except it's marketed with a name that makes it appear a safety/correctness feature.

Re: The C3 Programming Language

#258
post #246

Earlier quoted context omitted.

IMHO 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', wh…

tagged unions (not enums, sorry) are not a dynamic type system concept. Actually, I would not be able to name a single dynamically typed language that has them. As for the memory allocation, I can't see why any object should have the size of the largest alternative. When I do the manual equivalent of a tagged union in C (ie. a struct with a tag followed by a union) I malloc only the required size, and a function rece…

> Actually, I would not be able to name a single dynamically typed language that has them.

That's because every type in a dynamically typed language is a tagged union ;) For instance in Javascript you need to inspect a variable with 'typeof' to find out if it is a string, a boolean, a number or something else.

In a dynamically typed language, the runtime system needs to carry information around what type an item actually is, and this is the same thing as the type-tag in a tagged union - and Rust's match is the same sort of runtime type inspection as the typeof in JS, just with slightly different syntax sugar.

> As for the memory allocation, I can't see why any object should have the size of the largest alternative.

When you have a Rust enum like this:

    enum Bla {
        AByte(u8),
        AString(String),
        AStruct{ x: i64, y: i64 },
    }
...then every Bla object is always at least 16 bytes even when the active item is 'AByte' (assuming an empty String also fits into 16 bytes). Plain unions in C have the same problem of course, but those are rarely used (the one thing where unions are really useful in C (not C++!) is to have different views on the same memory).

> When I program in a language that has them then it's probably a sizeable fraction of all the types I define

...IMHO 'almost always sum types' is a serious design smell, it might be ok in 'everything is a reference' languages like Typescript, but that's because you pay for the runtime overhead anyway, no matter if sum types are used or not.

Re: The C3 Programming Language

#259
post #35

Just 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.

There is https://github.com/c3lang/c3c/issues/829

Re: The C3 Programming Language

#260
post #253

Earlier quoted context omitted.

That's the kind of thing I was thinking about. You can solve that with a switch in C3, but it's not as nice. However, this accounts for no more than 1% of all my goto uses (from a quick inspection), which is too little to build a feature from (discipline is needed to prevent a language from ballooning, it's hard to say no). I am looking for some use for it that can redeem its inclusion.

I agree it's very rare. I have this goto. A few that go to common return block that can be handled by other means and the rest is either error handling or things handled by labelled break or switch. I mean we know you can program without it and defer/labelled switch and labelled break/continue cover 99%+ of use cases of it. I am still not convinced those are in fact easier to read but I get it's a reasonable design c…

They have different wins. I think labelled break/continue help because they are clearer in locally expressing what the point is. If you see `goto NEXT;` you can kind of guess the intention, but `continue OUTER;` doesn't require you to read the code at the label and check what's happening there. `defer catch` and `defer try` helps avoiding some booleans otherwise needed with just a basic defer. `defer` on its own otherwise sometimes needs booleans to track what should be closed. With goto those naturally go to different cleanup sections.

I keep revisiting goto though. I like it a lot for its simplicity.

Post reply on HN