Hacker News new | past | comments | ask | show | jobs | submit login

After you've been writing zig for a while, seeing `.{}` in an argument list intuitively means "default arguments".



Seems like it could just be elided entirely. Why can't it be?


I do not know Zig, but it looks like it just means "call default constructor for parameter/variable/type". I do not see how you could expect it to be elided unless every function auto-constructs any elided arguments or always has default arguments.

In other words, for a function f(x : T), f(.{}) is f(T()), not f(), where T() is the default constructor for type T.

If we had a function with two parameters g(x : T, y : T2) it would be g(.{}, .{}) which means g(T(), T2()), not g().

It looks like the feature exists to avoid things like:

x : really_long_type = really_long_type(), which can be replaced with x : T = .{} to avoid unnecessary duplication.


I do not know Zig either; I had assumed that it has default parameters, but it seems that it does not[0]. So, yes, it makes sense now why it cannot be elided.

They should add default parameters to avoid this sort of thing. Maybe they ought to consider named/labelled parameters, too, if they're so concerned about clarity.

0: https://github.com/ziglang/zig/issues/484


Zig believes that all code needs to be explicit, to prevent surprises- You never want code that "just executes on its own" in places you may not expect it. Therefore, if you want default arguments, you have to perform some action to indicate this.


Except it's not entirely explicit. It allows the type name of the object being constructed to be elided.

Per the article, this is the explicit form:

    var gpa = std.heap.GeneralPurposeAllocator(std.heap.GeneralPurposeAllocatorConfig{}){};


I don’t think type elision make the codes execution less explicit. Nothing else could go there


That's a textbook definition of "implicit", as in not directly specified, but assumed.

The fact that unacceptable parameter would fail compile time validation does not make it any more readable.


Consider this:

    var foo = OpaqueTypeName(.{}){};
What is the . eliding?

You don't know. I don't know. It's impossible to tell because the type is opaque to our understanding.


i don't get this argument. what is code that "just executes on its own"? how is it more difficult to differentiate what a function does with vs without arguments compared to one that takes arguments with values vs arguments without values?


explicit about branching and allocations, not so for types. we've recently got .decl() syntax, which is even more implicit than .{}


Declaring a variable doesn't initialize it in Zig, so maybe the correct semantics in ellisions would be to allocate an unitialized argument.


For the same reason you can't pass a Python function expecting a list an empty list with foo(), you have to use foo([]). They mean different things.


However, in Python, if you routinely call foo([]), you'd specify that (or rather an empty tuple since it's immutable) as the default value for that argument.


I believe that if most foo's users should just call it with [], the Pythonic way is to make the argument optional.


Well yes, but if it's someone else's library, realistically you're not going to change it.

Zig is a static language without variadic parameters, so you can't make it optional in that sense. You could make the options a `?T` and pass `null` instead, but it isn't idiomatic, because passing `.{}` to a parameter expecting a `T` will fill in all the default values for you.


This doesn’t answer the question why Zig doesn’t have default argument values.


Default argument variables create variadic functions.

Arity N when you supply a value

Arity N-1 when you use the default


How does this create variadic functions? The arity is the same, since the function signature defines the exact amount of arguments. The compiler just passes the omitted ones for you.


Okay, but why could a static language not have variadic functions?


That's their design choice.

I can think of a few reasons

- makes function calling simpler

- faster compilation

- less symbol mangling

- makes grepping for function implementation easier

If for some reason you think you absolutely can't live without variadic functions, maybe don't use zig.


i have never used zig before, but after reading the article i came to the same conclusion. the "problem" (if it is a problem at all, that is) really is that .{} is the syntax for a struct whose type is to be figured out by the compiler, that new users will be unfamiliar with.

i don't know if there are other uses for . and {} that would make this hard to read. if there are, then maybe that's an issue, but otherwise, i don't see that as a problem. it's something to learn.

ideally, each syntax element has only one obvious use. that's not always possible, but as long as the right meaning can easily be inferred from the context, then that's good enough for most cases.


One will quickly become accustomed to the .{} pattern learning Zig. It's used for struct constructors:

  const a_struct: StructType = .{ .foo = "baz"}; 
As well as union initialization. So .{} is a struct constructor for a struct where every field has a default value, which chooses all of those fields.


I think it becomes clearer when considering that these are all equivalent:

    const a_struct: StructType = StructType{ .foo = "baz" };

    const a_struct = StructType{ .foo = "baz" };

    const a_struct: StructType = .{ .foo = "baz" };


> that new users will be unfamiliar with

It's really not much different than in nearly any other language with type inference except for the dot (which is just a placeholder for an inferred type name).




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: