Functions taking generic arguments can be implicitly or duckly typed, taking var and maybe making use of @typeOf:
math.max(a: var, b: var) -> @typeOf(a + b)
mem.writeInt(buf: []u8, value: var, big_endian: bool)
Or they can be explicitly typed, taking the type as an argument:
mem.max(comptime T: type, slice: []const T) -> T
endian.swap(comptime T: type, x: T) -> T
That there are these two different ways to do this seems to go against the Zen of Zig, in particular “Only one obvious way to do things”.
Proposal 1: Ban/disfavor var
For the callee, a var is essentially just a type and a value bundled together, so changing a function to take each separately is usually trivial (there might be an issue with passing integer literals, but I don't believe it would be a big problem to reify them to a concrete integer type instead).
For the caller, things are different: we introduce an entire new argument! This is a loss of ergonomics, but maybe a win in clarity?
Some pain points would be functions generic over multiple types that usually, but not always, are the same, e.g.:
math.max(comptime T: type, a: T, comptime U: type, b: U) -> ???
I’m not sure this is sufficiently better than using
math.max(comptime T: type, a: T, b: T) -> T
and having the arguments be cast to a parent/wrapper type, though.
Functions taking ... arguments would also need to pass the type on to its called functions, but I don't think this would be much of a problem in practice.
Proposal 2: Embrace var
Use var everywhere. This would mean rewriting the above functions to:
mem.max(slice: var) -> @typeOf(slice).Child
endian.swap(x: var) -> @typeOf(x)
This will certainly make the functions more ergonomic to call, but there are two major problems:
-
Using var essentially bypasses the explicit type system. In particular, it's now impossible to declare mem.max's slice to be a slice; you'll have to rely on duckly typing or casts/assertions in the function body itself.
-
The error messages will generally be less helpful, since it's impossible to type-check arguments up front.
As a very minor point, this is equally unergonomic as Proposal 1 when passing integer literals to functions:
endian.swap(i32, 1)
endian.swap(i32(1))
In addition, some functions will still require an explicit type to be passed:
mem.readInt(bytes: []const u8, comptime T: type, big_endian: bool) -> T
ArrayList(comptime T: type) -> type
Proposal 3: Compromise, somehow
Embrace that some kinds of functions should take explicit types, while others should take implicit types, essentially separating function-like functions from macro-like functions. I can't think of a good metric for what functions would fit in which categories.
I’m partial towards Proposal 1, but am aware it might be the most extreme of the three.
I think it’s important to decide on a policy; not necessarily one of these three. If I’m misunderstanding something, or I seem confusing/confused, please let me know!
I’m very curious to see how the language will evolve. :)
Functions taking generic arguments can be implicitly or duckly typed, taking
varand maybe making use of@typeOf:Or they can be explicitly typed, taking the type as an argument:
That there are these two different ways to do this seems to go against the Zen of Zig, in particular “Only one obvious way to do things”.
Proposal 1: Ban/disfavor
varFor the callee, a
varis essentially just a type and a value bundled together, so changing a function to take each separately is usually trivial (there might be an issue with passing integer literals, but I don't believe it would be a big problem to reify them to a concrete integer type instead).For the caller, things are different: we introduce an entire new argument! This is a loss of ergonomics, but maybe a win in clarity?
Some pain points would be functions generic over multiple types that usually, but not always, are the same, e.g.:
I’m not sure this is sufficiently better than using
and having the arguments be cast to a parent/wrapper type, though.
Functions taking
...arguments would also need to pass the type on to its called functions, but I don't think this would be much of a problem in practice.Proposal 2: Embrace
varUse
vareverywhere. This would mean rewriting the above functions to:This will certainly make the functions more ergonomic to call, but there are two major problems:
Using
varessentially bypasses the explicit type system. In particular, it's now impossible to declaremem.max'ssliceto be a slice; you'll have to rely on duckly typing or casts/assertions in the function body itself.The error messages will generally be less helpful, since it's impossible to type-check arguments up front.
As a very minor point, this is equally unergonomic as Proposal 1 when passing integer literals to functions:
In addition, some functions will still require an explicit type to be passed:
Proposal 3: Compromise, somehow
Embrace that some kinds of functions should take explicit types, while others should take implicit types, essentially separating function-like functions from macro-like functions. I can't think of a good metric for what functions would fit in which categories.
I’m partial towards Proposal 1, but am aware it might be the most extreme of the three.
I think it’s important to decide on a policy; not necessarily one of these three. If I’m misunderstanding something, or I seem confusing/confused, please let me know!
I’m very curious to see how the language will evolve. :)