> Those language features are not examples of dependent types, though...

Ironically C is the only language in that list with a dependent type. It's not _Generic, of course, but variable-length arrays (VLAs), which neither C++, Rust, nor Zig have.

D has variable length arrays as a builtin type. They are extremely useful.

C VLAs are a terribly clumsy design. Here's the right way to do it in C:

https://www.digitalmars.com/articles/C-biggest-mistake.html

That was basically Dennis Ritchies' proposal as well: Dennis Ritchie, Variable-Size Arrays in C, September 1990, http://jclt.iecc.com/Jct22.pdf#page=5

IIRC, one of the problems there is how to generalize it to, e.g., struct members. (Maybe you can speak to that.) The approach adopted by C99[1], being based on dependent typing, was I think better conceptually. But despite dependent typing being awesome, it manifestly was and still remains a tough sell to compiler implementors, so VLAs/VMTs have languished in C and have yet to be fully developed. I think I'd agree C would have been better off with the approach you suggest. Adoption likely would have been quicker, and it could have been implemented in full almost immediately.[2] The consolation prize is that hopefully C will soon see some progression on the VMT front, specifically the proposals under discussion on the committee for forward-referencing in function parameter VMTs and VMT struct array members. The final piece of that puzzle would be addressing the scary syntax for VLA passing, which feels error prone (C2y countof operator semantics help a little bit).

[1] Tom MacDonald, Variable Length Arrays, December 1989, http://jclt.iecc.com/Jct13.pdf#page=67

[2] Ironically GCC already had nascent VLA support, which may have helped nudge the C99 committee toward that instead of adopting Ritchie's approach. But the fat pointer approach has a more obvious and straight-forward implementation.

FWIW, I was not aware of Ritchie's proposal until after my article was published, and after the variable sized arrays were in D for many, many years.

> how to generalize it to, e.g., struct members. (Maybe you can speak to that.)

I don't know what you mean by that?

One issue with D's design is that it uses

char a[..]

as syntax for a pointer, not an array. C has the same confusing issues in prototypes where it allows char[] notation for pointers, but this as it is widely seen as a mistake.

I do not agree that C VLAs are a terribly clumsy design. Especially from the point of dependent typing, C's design is the right one, e.g. you can express something such as

void transpose(int X, int Y, double (out)[X][Y], const double (in)[Y][X]);

If you only have existential types and not the fully dependent form, then this is not possible.

D does not use `char a[..]` as syntax for anything.

A pointer is declared as `char* p`, and an array as `char[] p`.

A two dimensional array parameter is declared as:

    void transpose(double[][] a);
Having to specify the dimensions as separate parameters is an unfortunate design choice:

1. It's clumsy to have to carry the dimensions around as separate items, rather than as one item

2. If the wrong X or Y is supplied, you have a massive memory corruption problem

I took the syntax from your article, maybe it is not what D is using now.

It is more expressive to pass the dimensions as separate variables. This is also exactly what other dependently typed languages do. The memory corruption can be avoided by having the compiler check this. I agree though that fat pointers are more convenient and would should also have them as syntax sugar on top of it.

The syntax in the article is what I proposed for C. Sorry that wasn't clear.

Yes, the separate variables is more expressive. It's also clumsy, inelegant, and error-prone. It also only works for function calls - not fields in a struct.

the pointer stars got lost, sorry.

  void transpose(int X, int Y, double (*out)[X][Y], const double (*in)[Y][X]);

C also ‘supports’ fixed-length arrays. You can write

  int foo(int bar[10]) {
    …
  }
and you can tweak that to generate warnings when users pass arguments of the wrong size. See https://lwn.net/Articles/1046840/

That particular syntax has far too many caveats to be safely employed, IMO. It would be better to use variably-modified types (VMTs), which currently is more-or-less[1] a pointer to a VLA:

  int foo(int (*bar)[10])
In particular, sizeof(*bar) and (in C2y) countof(*bar) will do the correct thing. But you need to dereference bar almost every to use it properly, and unfortunately sizeof(bar) instead of sizeof(*bar) will compile cleanly, though countof(bar) is guaranteed to fail.

The dilemma is C's array to pointer decaying semantics. C2y will try to roll that back somewhat (a[i] will no longer be identical to i[a] via the a[i] == a + i == i + a transitive relation), but there's only so much you can do in that regard, especially for array passing, without new syntax or silently breaking alot of valid, proper, carefully written C code. There's too much that can fall through the cracks by relying on non-standard compiler heuristics to substitute for proper typing.

[1] Strictly speaking a VLA can only be created as automatic storage objects, but a VMT can type dynamically allocated (malloc'd) memory. But the syntax looks like a pointer to a VLA because it originates from the need to pass actual VLAs as parameters.