> Why is omitting the fact that T is a type useful?
It's the default, because most templates are templated on types. If you want a constant int as part of the template type,
ArrayListType(int I)
> This reminds me of C in the 1970s where the compiler assumed every typo was a new variable of type int
I think you're referring to function declarations without prototypes. D's syntax does not suffer from that issue.
BTW,
T func(T)(T x) { return x + 1; }
is a declaration of a "function template". The first parameter list consists of the template parameters, and are compile time types and constants. The second parameter list is the conventional function parameter list. If the first parameter list is omitted, then it's a function.
Walter is showing the equivalent function declaration... you will eventually create a generic type as you say, but in the Zig example, that was a function, not a struct.
If my understanding of D's template syntax is correct, then Walter is showing a the declaration of a function called ArrayListType which is generic over T and returns a T. The original Zig code returns the struct type itself, so it is functionally equivalent to my example, provided I understood how D templates work.
The Zig code returns any `type`, it's impossible to say what that is without looking at the implementation. It can be different types completely depending on the comptime arguments.
But I agree it probably returns a struct type.
Assuming that's the case, you're right and the equivalent would be:
Zig:
fn ArrayListType(comptime T: type) type {
D:
ArrayList!T ArrayListType(T)() {
But now the D version is more specific about what it returns, so it's still not exactly equivalent.
No, you misunderstand. The function doesn't return any type, it returns _a_ type. Types are values in Zig and returning them from function is how generics are implemented.
I know how Zig works. `type` is some type the function will return, you must look at the implementation to know what actually got returned, given the comptime arguments given to it by the caller (as I already mentioned). Where is the misunderstanding??
It achieves the same result in practice. The reason Zig needs to return a type is because it lacks a way to represent `T!A` where T is a parameterized type, and A is the parameter. In this case, that would've been better because you would be able to tell exactly what the type being returned was.
If you must return different types depending on the argument in D, it's also possible.
Here's a silly example:
struct ArrayList(T, alias capacity) {
private T[capacity] array;
private uint _length;
uint length() const => _length;
T get(uint index) const => array[index];
void add(T t) {
array[_length++] = t;
}
}
struct EmptyList(T) {
uint length() const => 0;
}
/// This will return a different type depending on the length argument,
/// which is like a Zig comptime argument.
/// We cannot return the type itself, but the result is very similar.
auto createArrayList(T, alias length)() {
static if (length == 0) {
return EmptyList!T();
} else {
return ArrayList!(T, length)();
}
}
void main()
{
import std.stdio;
auto empty = createArrayList!(int, 0);
writeln(empty);
auto list = createArrayList!(int, 2);
list.add(5);
list.add(6);
writeln(list);
}
It's now too late to edit my original comment but I've skimmed your code a little too fast and now I see that you do understand that Zig works with reified types. But in that case, why conflate too very different things? Returning values of different types is not the same as returning types.
Regardless, we are now very far removed from the original code I was commenting on, which doesn't use auto to return different types.
You still misunderstand the type type. "type" does not mean "generic over all types" but that you're returning a reified type. The original Zig function does not return an ArrayList value, it returns the ArrayList type.