[HN Gopher] Zig's (.{}){} Syntax
       ___________________________________________________________________
        
       Zig's (.{}){} Syntax
        
       Author : todsacerdoti
       Score  : 77 points
       Date   : 2024-11-05 12:23 UTC (4 days ago)
        
 (HTM) web link (www.openmymind.net)
 (TXT) w3m dump (www.openmymind.net)
        
       | kristoff_it wrote:
       | As people have pointed already elsewhere, the same declaration
       | can be made more clear by isolating the type like so:
       | var gpa: std.mem.GeneralPurposeAllocator(.{}) = .{};
        
       | kcbanner wrote:
       | After you've been writing zig for a while, seeing `.{}` in an
       | argument list intuitively means "default arguments".
        
         | dleslie wrote:
         | Seems like it could just be elided entirely. Why can't it be?
        
           | samatman wrote:
           | 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.
        
             | owl57 wrote:
             | I believe that if most foo's users should just call it with
             | [], the Pythonic way is to make the argument optional.
        
             | int_19h wrote:
             | 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.
        
           | Veserv wrote:
           | 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.
        
             | dleslie wrote:
             | 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
        
           | AlienRobot wrote:
           | Declaring a variable doesn't initialize it in Zig, so maybe
           | the correct semantics in ellisions would be to allocate an
           | unitialized argument.
        
       | j-krieger wrote:
       | A whole lot of cleverness for a language that refuses to compile
       | when you have unused parameters.
        
         | conradev wrote:
         | With the Zig language server it's not terribly annoying:
         | 
         | _ = foo; // autofix
        
         | AlienRobot wrote:
         | And has no multi-line comments.
        
         | MineBill wrote:
         | If you want to see true cleverness just go see the last devlog
         | on the zig website.
        
           | mtlynch wrote:
           | I think this is the post parent is referencing:
           | 
           | https://ziglang.org/devlog/2024/#2024-11-04
           | 
           | It seems like an interesting idea, but I wish Andrew spent
           | more time fleshing it out with complete examples. I can't
           | tell if the _ characters are eliding values or if that's
           | literally what's in his code.
        
             | defen wrote:
             | It's Zig's equivalent of the newtype idiom:
             | https://doc.rust-lang.org/rust-by-
             | example/generics/new_types... for integers.
             | 
             | The underscores mean that it's a non-exhaustive enum. An
             | exhaustive enum is where you list all the names of the
             | possible enum values, and other values are illegal. A non-
             | exhaustive enum means any value in the underlying storage
             | is allowed. So at root this code is creating a bunch of new
             | integer types which are all backed by u32 but which can't
             | be directly assigned or compared to each other. That means
             | you can't accidentally pass a SectionIndex into a function
             | expecting an ObjectFunctionImportIndex, which would be
             | impossible to do if those functions just took raw u32's.
        
             | fallingsquirrel wrote:
             | It's an interesting pattern, but it's a shame there's no
             | way to efficiently use one of those OptionalXIndex with
             | zig's actual null syntax, `?` and `orelse` and etc. It
             | would be smoother if you could constraint the nonexhaustive
             | range, and let the compiler use an unused value as the
             | niche for null. Maybe something like `enum(u32) { _ =
             | 1...std.math.maxInt(u32) }`
        
           | quotemstr wrote:
           | That's just newtype.
        
         | norir wrote:
         | I am not a fan of zig, but I am a fan of discipline so I like
         | this particular design decision.
        
           | alpaca128 wrote:
           | I would be fine with it if it only threw an error about that
           | when building in release mode or if there was a flag to
           | silence it temporarily.
           | 
           | But while trying out some things and learning the language I
           | find it annoying. And I don't know how it makes me more
           | disciplined when I can just write `_ = unused;` to suppress
           | the error. Even worse, if I forget that assignment in the
           | code the compiler will never warn me about it again even when
           | I want it to.
           | 
           | So far I haven't seen any upside to this.
        
           | amelius wrote:
           | If you're a fan of discipline then you could also just call
           | lint (or equivalent) before compiling.
        
       | andout_ wrote:
       | The article spends a lot of time justifying a syntax that really
       | just papers over Zig's lack of parameter pack support. The same
       | pattern in Rust would just use variadic templates/generics.
        
         | samatman wrote:
         | This comment might be valuable if you added some elaboration,
         | and an example of what it looks like.
        
         | fallingsquirrel wrote:
         | > The same pattern in Rust would just use variadic
         | templates/generics.
         | 
         | Are you sure Rust has variadic generics? Last I checked, the
         | extent of progress was a draft RFC. https://github.com/rust-
         | lang/rfcs/issues/376
        
       | samatman wrote:
       | I'm glad I read the last line, for those who may not have gotten
       | that far: this is about to become a much less prevalent pattern
       | in Zig code, replaced with declaration literals. The new syntax
       | will look like this:                  var gpa:
       | std.mem.GeneralPurposeAllocator(.{}) = .init;
       | 
       | Which finds the declaration literal
       | `std.mem.GeneralPurposeAllocator.init`, a pre-declared instance
       | of the GPA with the correct starting configuration.
        
       | kvark wrote:
       | That "." substitution of an inferred type is going to fire back.
       | I really appreciate when code has one simple property: you search
       | a type by name and you get all of the places where it's
       | constructed. Makes it easy to refactor the code and reason about
       | it with _local_ context. It's the case with Rust, but not C++ or
       | Zig.
        
         | zamadatix wrote:
         | What's meaningfully different in Rust's type inference. E.g.:
         | fn example() {           let p = returns_a_point_type(args);
         | }
         | 
         | Where create_point() is a function from a module (e.g. not even
         | defined in that file) which returns the Point type
         | automatically inferred for p? I mean sure, it's technically
         | constructed in the called function... but is that often a
         | useful distinction in context of trying to find all of the
         | places new instances of types are being assigned? In any case,
         | this is something the IDE should be more than capable of making
         | easier for you than manually finding them anyways.
        
           | nindalf wrote:
           | GP is talking about how easy it is to find places where the
           | type is instantiated. Seems to me that create_point() will
           | have one such site. And then it's trivial to find callsites
           | of create_point() with the LSP/IDE. What's the issue?
        
             | zamadatix wrote:
             | The IDE can find all places new variables are assigned to
             | the type (regardless of whether it's direct instantiation,
             | return value, inferred, or whatever way it comes about) so
             | what's the special value of being able to manually find
             | only the local instantiations find ctrl+f if you'd still
             | need to manually track down the rest of the paths anyways?
        
         | int_19h wrote:
         | Any IDE worth its salt will let you search a type by name and
         | get all the places where it's referenced, regardless of type
         | inference.
        
           | alpaca128 wrote:
           | A language that promotes itself as simple and with no hidden
           | control flow etc shouldn't need an IDE to find hidden things
           | imho.
           | 
           | But that kind of shortcut seems to be optional.
        
             | int_19h wrote:
             | "No hidden control flow" is completely orthogonal to "no
             | implicit typing". I think anyone looking at Zig would
             | immediately recognize that it is firmly in the type
             | inference camp by choice.
             | 
             | As far as simplicity, I think their pitch is "simpler than
             | Rust", not in absolute terms. The whole comptime thing is
             | hardly simple in general.
        
       | kazinator wrote:
       | It's amazing they couldn't figure out how to get f(.{}) down to
       | just f({}). Like here is this brace enclosed thing being matched
       | against the argument type.
        
       ___________________________________________________________________
       (page generated 2024-11-09 23:00 UTC)