Open SpexGuy opened 4 years ago
I agree, and personally like anyvalue
and anyval
.
edit: not sure if I agree with myself anymore
Important part was missed - declaration.
i: u32 // i is u32 variable
T: type // T can be type only
writer: anytype // writer is variable with any type, not unknown value
What's problem?
+1 for anyval
. I think that's much better.
I agree, and personally like
anyvalue
andanyval
.
Seconded. I found it terribly confusing when the ZIg formatter started changing my var
S to anytype
S. I would have expectd anytype
to really be a type, but it's apparently just the new name to var
.
Important part was missed - declaration.
i: u32 // i is u32 variable T: type // T can be type only writer: anytype // writer is variable with any type, not unknown value
What's problem?
@data-man Something like so:
const std = @import("std");
fn printAnyType(obj: anytype) void {
std.debug.print("{}, {}, {}\n", .{ obj.@"foo", obj.@"bar", obj.@"baz" });
}
test "printAnyType" {
printAnyType(.{ .foo = "Hello", .bar = 1, .baz = false });
}
where I would have assumed anytype
to be a type, not a value. Instead, it is sort of like a generic object, a value. In a purely functional dependently-typed language like Idris, maybe types and values are truly equivalent, but in an imperative language, it feels jarring.
+1 for anyvalue
since we have anyerror
not anyerr
, but anyval
sounds nice too.
My opinion:
A value of anyerror
is an error
value. anyerror
is the "supertype" of all more specialized error
types.
A value of anyframe
is a frame
value. anyframe
is the "supertype" of all more specialized frame
types.
A value of the proposed anyvalue
/anyval
will be... a value
value. Which kind of makes sense, because Zig treats everything, including types, conceptually as values. But when asking "what types of values can I expect" the answer is "value values". It doesn't seem quite as intuitive to me.
I suggest plain any
. Then it's clear it's whatever you can think of - functions, types, values, whatever you can pass as an argument / bind to the name.
I think it's also clearer that any
is even less specialized than anyerror
and anyframe
.
I hard disagree with this. Personally, I believe a type definition answers the question "What type is this?".
a: anyerror
-> The type of a
is any error.
b: anyframe
-> The type of b
is any frame.
c: anytype
-> The type of c
is any type.
d: u32
-> The type of d
is u32.
Nothing is ambiguous, the only reason you have an ambiguity here is because you are omitting the "The type of" statement and transforming the statement around a bit (which is ususally fine) but for some reason you doing it incorrectly for anytype.
d: u32
-> d
is a u32
But instead of this: c: anytype
-> c
is an any type (which is a weird thing to say, but its correct)
You're saying this c: anytype
-> c
is any type. (wrong, and can be interpreted wrong as saying that c
itself is a type).
Additionally there is a bit of a misunderstanding conflating values themselves to types to superclasses of types.
A variable that holds some unknown value is an anytype?
A variable that hold some unknown value is just a redundant way of saying "a variable".
Up there, d
is also has some unknown value, the value has nothing to do with it, you can clearly see why anyvalue
is wrong below.
e: anyvalue
-> The type of e
is any value [ of any type?]
This suggests that [the type of] e
could be a value like 2
. But obviously the type of e
must still be a type (hence anytype
).
Anyway for more on superclasses, anyframe
isn't actually a specific type, it's a superclass where any specific frame
type for each function can be transformed into it. Same for errors, anyerror
is a superclass where any specific error
enum type can be turned into it.
I understand why all this confusion specifically happens for anytype
though because Zig treats types like values. So you have to deal with strange (at first glance) statements like this.
c: antype
-> c
is an any type (including a type itself)
Then the misinterpretation "c
is any type" isn't unambiguously wrong, because technically c
could be a type.
Anyway I'm sorry if my response is long and convoluted, but please read through it again if you don't understand. I spent a long time thinking about why this was wrong but I couldn't quite put it into words. It might not be abundantly clear, but I assure you, antype
is not ambiguous and is correct.
@AssortedFantasy You seem to be putting a lot of weight on a specific translation of Zig syntax into English, and moreover on a specific syntactic ambiguity which this particular translation has. There are other ways of thinking, and many of them arrive at different places. Just because something makes sense to you doesn't mean it's the only or best possible way of thinking.
anyerror
is the supertype of all errors.
anyframe
is the supertype of all frames.
anytype
is...the supertype of all types? Well, yes, but that's hardly a faithful description.
but I assure you,
antype
is not ambiguous and is correct.
This is rather dismissive and rude. Also, from the very fact that this thread exists, evidence suggests that its intent and meaning are not as clear as you're implying.
Easy there, I can sense the passions in this thread escalating a bit. Let's all take a moment to remind ourselves that we have each others' best interests in mind, and try to keep the arguments focused on the technical bits. One thing to remember is that the ultimate decisions here will be based on the technical points made, and nothing else. If somebody makes a bad argument, I promise you I know it is a bad argument, you can just let it slide. Please, for my sake :sweat_smile:
@EleanorNB @timmyjose I understand I might come off as a bit aggressive, Sorry about that and I'll try to be a bit more agreeable from now on if that makes for better debate.
I was trying to avoid the word "supertype" and "superclass" because its not really what I meant, it implies that it's somehow a different structure than a type itself, a higher level abstraction of sorts, but its not, it's just a union of types (which is still just a type).
Let's get away from shakey language and maybe I can show my same point again with pure mathematics, then I can show why I personally believe anytype
is a faithful and consistent name.
In mathematics, when you're being formal, you need to specify the domain (and codomain) of your functions when you define them.
For a function f(x,y) = ln(x)+y you'd need to specify something like x ∈ R+ and y ∈ R. I believe that Zig should try to match this.
Basically I think the little :
is supposed to be an element of symbol.
So then fn foo(x: u32, y: i32) i32
means
x
∈ u32
and y
∈ i32
.
anyerror
is a cool thing that's supposed to be the equivalent of a mathematical union of all errors. anyframe
is supposed to be a union of all frames, and lastly anytype
is supposed to be a union of all types.
Basically I don't believe
fn bar(k : anytype)
is ambiguous, because a union of types makes perfect sense mathematically.
k
∈ u0
∪ u1
∪ ... ∪ i0
∪ i1
∪ ... ∪ f64
∪ ... ∪ struct {a: i32, b:i32}
∪ ... type
The above is correct, k actually is an element of any of those things. And it also shows why I don't like using anyvalue
, In my opinion any value doesn't make sense because a type itself is just a union of values. So 2 is a value, an instance of a point struct is a value, but k
is not an element of 2, and k is not an element of a instance of a point struct.
I hope maybe this makes a bit more sense and is less angry sounding.
To complete my argument from the last thing, my point about how the conflating confusion shows up is also shown in the mathematics. The very last thing at the end of the union, type
happens to be a set containing everything that makes up that anytype
union, including type
itself. Which technically isn't mathematically sound (violates axiom of regularity) and makes the language a bit confusing, but ¯\_(ツ)_/¯, at least it's rigorous.
type
= { u0
, u1
, ... , i0
, i1
, ... f64
, ... ,type
}
Since unions of types are still types (which are themselves sets of values) then type is a powerset of anytpe. Regardless, even with this weird recursive structure a single value by itself never appears on the left hand side (though all sets containing a single value does). So you can again see.
2
∩ anytype
= {} (because 2 is not a type).
The conflatement happens becuse [in Zig] a union of all types is not completely different from a union of all values (anyvalue
). Specifically the intersection is the set of all types, because types are values (but values are not types).
anytype
∩ anyvalue
= type
One way to linguistically resolve this inconsistency is to say that anyvalue
is not the union of all values. But is instead a set containing all values (the set of all values would be a type).
This seemed reasonable until I realized that the only reason this makes sense grammatically is that it manages to avoid defining/mentioning the domain that you're talking about. It's the set of all things in some unmentioned universe of values. It's poorly defined, you might take it to be any value of a f128
for example, but it's not, its any value of a ... anything? It's explicitly mentioned what this universe is with the word anytype
(when its taken to mean the union of all types), this universe is anything that can be an element of any Zig type.
TLDR; I think the fact that anytype
makes functions generic but anyerror
and anyframe
don't warrant a different syntax and/or naming scheme
I think the confusion here is caused by an inconsistency with the "any*" types.
anytype
is not a type in and of itself. It is a "placeholder" for other types. Declaring an anytype
parameter is what makes functions generic. The word "any" in this case refers to the type itself meaning it can be substituted with "any type".
This is in contrast with anyerror
and anyframe
which are actual types and do not make functions generic. They are union types which can hold "any value" from any error type or any frame type respectively. Here the word "any" means the type that can represent "any value", whereas with anytype
, "any" means the type is a generic placeholder that accepts "any type".
const std = @import("std");
fn takeAnyError(x: anyerror) void {}
fn takeAnyFrame(x: anyframe) void {}
fn takeAnyType(x: anytype) void { }
pub fn main() void {
std.debug.print("{}\n", .{&takeAnyError});
std.debug.print("{}\n", .{&takeAnyFrame});
// compile error because takeAnyType is generic
//std.debug.print("{}", .{&takeAnyType});
}
At first glance, all 3 takeAny*
functions appear as if they would behave similarly, but they are actually very different because only takeAnyType
is generic. Regardless of the actual names, I think using a different naming convention to distinguish between generic and non-generic behavior would be more clear. Whether that means renaming anyerror
/anyframe
or anytype
doesn't seem important but having a clear way to distinguish between them seems more so.
That is a good point. Perhaps vartype
? That communicates that the type is variable, and doesn't overload syntax.
I like vartype
, but type is not variable - it's constant, we just don't know which.
fn a(b: vartype) void {
// @TypeOf(b) is *constant*, and will never change within a function permutation
}
How about something that indicates generic type?
That's not correct, the type can change if the variable is not const
. This code is perfectly valid:
comptime {
const Any = struct { value: anytype };
var a = Any{ .value = "foo" };
a.value = 4;
a.value = Any{ .value = 3.0 };
}
I like any
. It has precedent in TypeScript.
I agree with what @SpexGuy said in his original comment:
any
might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it.
How about generic
, arbitrary
, value
, anything
, etc?
I agree with what @SpexGuy said in his original comment:
any
might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it.
In my experience error
and test
are much more common names than any
when writing in languages that don't reserve them.
I think the benefit of having a (in my opinion) nicer keyword is far larger than the occasional inconvenience of choosing a different name.
How about auto
?
I was about to suggest autotype.
I like autotype, I think it's the best option I've heard yet.
I might prefer auto
over autotype
, since it doesn't imply that the
value is of type type, but that might just be me.
With autotype
there would not be the same confusion as with anyerror
and such, since it would be the only one using "auto". Thus it "automatically" determimes the type.
C++ uses auto
for type inference, where the type cannot change. That might cause confusion for people learning the language, and I don't think it really conveys that the type can change. The more I think about it, the more I like any
. It's such a generic word that I think it might be ok for the language to reserve, especially since variables or functions with that name can be trivially replaced by "hasAny".
C++ uses auto for type inference, where the type cannot change. That might cause confusion for people learning the language, and I don't think it really conveys that the type can change.
@SpexGuy, you keep emphasizing this point, but I'm not sure I follow. The documentation only mentions anytype
for generic function arguments, which surely must remain constant after being inferred in a particular invocation. Your above example with anytype
struct fields is interesting, but I couldn't find any actual use of this in the Zig code base. Is this some kind of undocumented feature? Or even intended? I kind of struggle to imagine what a dynamic Any
type would even mean in a language that does not use boxed values and runtime dispatch.
@zzyxyzz
anytype
as a mutable field is quite a useful feature when writing comptime code in my opinion.
The feature is undocumented but intended.
When you use an anytype
field the container type becomes comptime-only.
Afaict atm this is only used in the standard library itself in some std.builtin.TypeInfo
structs (e.g. for default values of fields and sentinel values).
However, it is also very useful for building up tuples.
Here is an example from ctregex:
// args is `anytype`, will be apssed to a std.fmt function
// We intercept u21 unicode codepoint values and encode them as utf8
const ArgTuple = struct {
tuple: anytype = .{},
};
var arg_list = ArgTuple{};
for (args) |arg| {
if (@TypeOf(arg) == ?u21) {
if (arg) |cp| {
arg_list.tuple = arg_list.tuple ++ .{ctUtf8EncodeChar(cp)};
} else {
arg_list.tuple = arg_list.tuple ++ .{"null"};
}
} else if (@TypeOf(arg) == u21) {
arg_list.tuple = arg_list.tuple ++ .{ctUtf8EncodeChar(arg)};
} else {
arg_list.tuple = arg_list.tuple ++ .{arg};
}
}
I agree with @SpexGuy here, the fact that the type of an anytype
(/anyvalue/w.e.
) parameter is constant is just a side effect of the fact that the parameter itself is constant.
I kind of struggle to imagine what a dynamic Any type would even mean in a language that does not use boxed values and runtime dispatch.
The type of an anytype
cannot change at runtime. But comptime zig is an interpreted language with boxed values and type values and dynamic dispatch and everything, so the type of a variable stored in anytype
changing at comptime makes just as much sense as the type of a var
changing in JS. It's currently restricted to function parameters and struct members, but it could easily be extended to be allowed as the given type of a local comptime var
in the future. I'm hoping this happens, because the Any
struct workaround gives all the same functionality but is just more typing.
anytype
fields are currently used by std.builtin.TypeInfo.Pointer
to represent the sentinel. In order to robustly construct the type *<modifiers>[1]T
from *<modifiers>T
, for example, modifiers on the pointer need to be preserved. The maintainable approach is to use @typeInfo
to get a type info for the pointer and modifiers, alter the target type of the pointer, and then use @Type
to construct the altered type. But in order to do this, the type of the sentinel field must also change from @as(?T, null)
to @as(?[1]T, null)
. That requires a struct member that can change types at comptime, so this behavior probably isn't going away.
@alexnask, @SpexGuy Thanks for the explanations. Makes sense.
I would actually argue that this makes the two uses of anytype
quite distinct: One for creating generic functions and one for representing comptime mutable types. That both use the same keyword is economical, but confusing. Maybe the two uses should simply be split, with auto
indicating a generic parameter and any
or anyval
or whatever standing for a comptime dynamic type. Of course that would reserve two short keywords...
I propose anyitem as item is in English language more holistic than object. It conveys the expression of a very generic concept like an idea, a person, a place or even a physical or conceptual object.
As for me, anyitem may be adequate to signify any logical concept as abstact as a value of a value.
Anytype looks a bit misleading to me. Lets look if the type system is consistent:
anyerror
, variable/parameter compatible with all error values.anyframe
, var/param compatible with all frame pointers. type
, var/param compatible with all types. Requires comptime
. One of the earliest types. Sometimes its inconvenient that type
is a keyword.anytype
, its a type/keyword(??) that infers a type of a value.Lets see what TypeOf will produce:
pub fn abc(a: anyerror, b: anyframe, comptime c: type, d: anytype) void {
@compileLog(@TypeOf(a), @TypeOf(b), @TypeOf(c), @TypeOf(d));
}
comptime {
abc(undefined, undefined, i32, @as(i32, 5));
}
| anyerror, anyframe, type, i32
<source>:2:3: error: found compile log statement
@TypeOf(a) == anyerror; @TypeOf(b) == anyframe; @TypeOf(c) == type; @TypeOf(d) != ~anytype~
//@TypeOf(anytype);
//error: expected token ')', found 'anytype'
As you can see that symmetry does not hold. I believe that this type/keyword should not be prefixed with any
. The type of types may be renamed to anytype
, that would allow to create fields and variables named type
using the simple syntax.
What about expr
or expression
?
I kind of struggle to imagine what a dynamic Any type would even mean in a language that does not use boxed values and runtime dispatch.
The type of an
anytype
cannot change at runtime. But comptime zig is an interpreted language with boxed values and type values and dynamic dispatch and everything, so the type of a variable stored inanytype
changing at comptime makes just as much sense as the type of avar
changing in JS. It's currently restricted to function parameters and struct members, but it could easily be extended to be allowed as the given type of a localcomptime var
in the future. I'm hoping this happens, because theAny
struct workaround gives all the same functionality but is just more typing.
anytype
fields are currently used bystd.builtin.TypeInfo.Pointer
to represent the sentinel. In order to robustly construct the type*<modifiers>[1]T
from*<modifiers>T
, for example, modifiers on the pointer need to be preserved. The maintainable approach is to use@typeInfo
to get a type info for the pointer and modifiers, alter the target type of the pointer, and then use@Type
to construct the altered type. But in order to do this, the type of the sentinel field must also change from@as(?T, null)
to@as(?[1]T, null)
. That requires a struct member that can change types at comptime, so this behavior probably isn't going away.
There's a workaround for the anytype
restriction that mentioned:
const Any = @typeInfo(struct { a: anytype }).Struct.fields[0].field_type;
I'm in favor of (should this be changed) anything
: it's consistent with any...
type-category names, and coincides with a nicely relevant existing English word, but doesn't give any suggestion that the value is related to the type
type.
A couple more ideas I thought of after reading people's comments:
defer
or deferred
: rather than declaring a type, the function is choosing to "defer" that responsibility to something elsefn foo(x : defer) void {
}
placeholder
: instead of declaring the type, the function is creating a placeholder for a type to be filled in by something elsefn foo(x: placeholder) void {
}
I would avoid defer, since it's already used for deferred execution.
Since we are discussing generic function arguments, why not just say so:
fn foo(x: generic, y: @TypeOf(x)) {
...
}
I think this was already suggested by @pixelherodev, but didn't gain traction.
generic
also seems like a fair description of the other use-case mentioned in the thread -- comptime-typed struct fields and variables:
const ArgTuple = struct {
tuple: generic = .{},
}
// ... See @alexnask's comment above for rest of example.
If we're actually going to change it, I am strongly for generic
as this is self-explanatory and explains what it does
My vote: first choice any
, second anything
, third anyval
I agree with @vkcz, anything
works:
anyframe
anyerror
anything
But if the terms expression or value are more idiomatic in zig I would be fine with anyexpr
/anyexpression
or anyval
/anyvalue
.
Because I currently have important work to procrastinate on, I have carefully read through the entire thread and attempted a summary. My conclusion is that anytype
is actually not too bad, but if we do decide to change it, then we should go for something that specifically emphasizes the generic instantiation behavior, such as auto
, infer
or generic
. For the reasoning behind this conclusion, read on.
I'd like to draw attention to the fact that anytype
has two quite distinct uses:
comptime
implements semantics resembling a dynamic scripting language, a struct field declared as anytype
can change its type during comptime execution. This can be used, for example, to build up tuples programmatically. In the future, comptime dynamic variables might be allowed too. While not unimportant, this use case is both advanced and rare.From a certain technical perspective, the two cases can be regarded as two sides of the same coin: Both have dynamic variables with the type inferred from the supplied value. In that view, the inferred type of a generic function argument is actually dynamic, and acquires its constness only as a side effect of function arguments being constant in Zig. While technically correct, I find this view mostly unproductive. In practice, the difference between the two use cases is almost as big as the difference between languages with static and dynamic typing. At least in my head, they occupy quite distinct mental niches, although I can't vouch for the heads of other developers.
Because of this, using the same keyword in both cases will always feel kind of unsatisfactory. The semantics of either one or the other will be described poorly. One way out of this would be to simply use different keywords, but for reasons of keyword economy and the fact that the two are indeed somewhat related, I would say this is overkill. However, the inherent conflict between the two uses should be acknoledged and preferably resolved in favor of generic functions. This case is not only much more common, but also much more likely to be encountered by newcomers, so descriptiveness and clarity are important here. I would find it acceptable if the much more expert-level case of dynamic comptime fields suffers a bit of poor aesthetics in return.
With that out of the way, let's dig in. For the original motivation of this issue, I'll quote @SpexGuy:
The value of a variable of type anyerror is some error. The value of a variable of type anyframe is some frame. The value of a variable of type anytype is usually not a type.
Said another way, A variable that holds some unknown error is an anyerror. A variable that holds some unknown frame is an anyframe. A variable that holds some unknown type is a type. A variable that holds some unknown value is an anytype?
As pointed out by @AssortedFantasy, there seems to be a subtle error of reasoning in that last line. "A variable that holds some unknown value" doesn't actually say anything. It is the purpose of variables to hold unknown values. Of course, the range of these values may be constrained by the type of the variable to various extents:
anyerror
can hold any value from the union of all errorsets in the program.anytype
can hold an integer, a float, an error, an enum, or anything else, because anytype
is the union of all types. In PL theory it would be called top, or root of the type hierarchy.In short, anytype
is actually the appropriate term to describe the type of a variable that may hold anything. any
would do equally well.
Despite this, I'm actually in favor of changing it to something else. The above reasoning is more appropriate to case (2) mentioned in the prelude, that is, actual dynamically typed variables. But as argued before, case (1) is more important and anytype
(and especially any
) strongly suggests that you are dealing with a variable with unconstrained range. Instead, what happens is that you get a variable of a very specific and constant type in a separate function instantiation. This was pointed out by @marler8997: neither anyerror
nor anyframe
instantiate new functions, but anytype
does.
For the above reasons, I think that all anyX
proposals are actually solving the wrong problem. It is the any
in anytype
that we should take issue with, not the type
. This would rule out anyitem
, anything
, anyval
, anyvalue
. In my opinion, all of these are strictly inferior to anytype
or any
.
The proposals that I would recommend for consideration are those that express the generic function instantiation semantics directly. These include, in order of my personal preference, auto
, infer
and generic
. I'm less fond of autotype
, and defer
and defertype
I find positively confusing in this context.
To whom it may concern: Thanks for reading!
Though I personally don't have an issue with this, it can be argued that type
in anytype
is
type
, which may suggest that anytype
constrains the variable to holding type
values of any kind.Both of this is true, but the only correct solution in that case is any
, which is in fact what most
languages use to describe variables that can hold values of all types. Going for anyvalue
or anyitem
would be even even more redundant an misleading.
With that said, I think the difference between anytype
and any
is not that dramatic, and probably not worth changing. If we seriously contemplate a change, it should favor the "generics" interpretation, as argued in my above comment.
There is a universal 'don't care' syntax in zig - the underscore.
fn foo(x: _) void {
// i don't care what the type is, just give me the value.
}
maybe we could use bruh
with a similar sentiment as usingnamespace
fn foo(x: <bruh>) void {
// i don't care what the type is, just give me the value.
}
it's emoticon time (_:_)
A couple more ideas I thought of after reading people's comments:
defer
ordeferred
: rather than declaring a type, the function is choosing to "defer" that responsibility to something elsefn foo(x : defer) void { }
In that case...
if (defer == defer) {
defer _ = defer;
} else defer switch (defer) {
else => defer == defer
};
After reading the comments a second time, I am leaning towards 2 proposals:
Personally I still like anything
because it's consistent with the existing anyerror
and anyframe
and the upcoming anyopaque
.
Disclaimer: I'm rather new to this language -- been watching it for a while but haven't yet put it into much practice, until now. So I figured that maybe a newcomer's perspective might be useful here...maybe not...?
Today I was trying to learn the difference between x: anytype
and comptime T: type, x: T
and the nice folks over at zigforum.org helped get me through it.
I read the comments in this issue in their entirety, and there are some great ideas here. My takeaway is that anytype
, although not necessarily incorrect, is somewhat inconsistent with the other any*
keywords. As a newcomer to the language, I think it would be much easier to understand anytype
if it were changed to something more indicative of its generics use case. The auto
and generic
suggestions are my favorite.
Also, anytype
does make sense to me in the compile time use case (i.e. as a dynamic type).
I did have one (potentially crappy) solution -- just getting the idea out there. Since we can essentially accomplish the generic behavior of x: anytype
with comptime T: type, x: T
, maybe one way to deal with this is to remove anytype
as a function parameter, and just keep it as a struct field type (so we still have the dynamic type behavior at compile time). One argument in favor of this would be that the builtins use the comptime T: type, x: T
pattern already, so there's a consistency there. Just an idea...
Edit: ... well I guess @bitCast(comptime DestType: type, value: anytype) DestType
is a good example of how both patterns together is useful...
@Zooce, yes I've also found that anytype
is inconsistent with the other any*
keywords which can cause some confusion. I agree that changing it to something like auto
or generic
would be an improvement.
As far as I can tell, there's very little difference between x: anytype
and comptime T: type, x: T
besides ergonomics for the caller. I believe the caller can emulate the behavior of either one:
fn exampleAnytype(x: anytype)
fn exampleComptimeT(comptime T: type, x: T)
// emulate ComptimeT when calling a function that takes Anytype
exampleAnytype(@as(T, x))
// emulate Anytype when calling a function that takes ComptimeT
exampleComptimeT(@TypeOf(x), x)
In practice, there seems to be a number of factors that determine which one someone would choose. Accepting anytype can make it more convenient to call the function. If the function only accepts slice types, then declaring the signature as comptime T: type, x: []T
helps clarify what the function accepts more than x: anytype
would.
In my opinion it's not clear when to use which kind of signature, which doesn't jive with one of Zig's zen points "Only one obvious way to do things". I created an issue a couple years ago to discuss when the standard library should use one signature type over the other here: https://github.com/ziglang/zig/issues/2904
Those are good points (one way to do things and ergonomics). Personally, I would tend to favor the "only one way to do things" philosophy, but ergonomics (and also readability) are certainly important. Here's a bad example that ruins ergonomics (at least for me):
fn testMe(comptime OutType: type, comptime InType: type, x: InType) OutType {
if (OutType == bool) {
return x > 0;
} else {
return if (x > 0) 1 else 0;
}
}
test "bad example" {
const x: i32 = 5;
const u = testMe(u64, i32, x);
const b = testMe(bool, i32, x);
//...do some `expect`s
}
From the call site, having more than one type
anywhere in the function parameters, it's not at all clear to the reader what's going on. Maybe if Zig had parameter labels it would work...but still not great. Using anytype
for x
in this case is obviously better.
Sorry, this is kind of nitpicking, but it's been bothering me.
The value of a variable of type
anyerror
is some error. The value of a variable of typeanyframe
is some frame. The value of a variable of typeanytype
is usually not a type.Said another way, A variable that holds some unknown error is an
anyerror
. A variable that holds some unknown frame is ananyframe
. A variable that holds some unknown type is atype
. A variable that holds some unknown value is ananytype
?var
was a bad name because it was overloaded with the mutability specifier. But aside from that, IMO it was an ok name. I don't think we should restore it but I thinkanytype
is also confusing.any
might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it.infer
was suggested previously, but doesn't convey that the type can change over the variable's lifetime. Maybeanyvalue
,anyval
, orvartype
would be better?