[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)