[HN Gopher] Zig Quirks
       ___________________________________________________________________
        
       Zig Quirks
        
       Author : andreabergia
       Score  : 237 points
       Date   : 2023-03-27 06:55 UTC (16 hours ago)
        
 (HTM) web link (www.openmymind.net)
 (TXT) w3m dump (www.openmymind.net)
        
       | quietbritishjim wrote:
       | (Non-user of Zig here.)                   // tea.zig
       | full: bool = true,         const Self = @This();
       | pub fn drink(self: *Self) void {           self.full = false;
       | }              // other.zig         const Tea =
       | @import("tea.zig");
       | 
       | This example seemed to stop just short of the really interesting
       | bit: what if other.zig called Tea.drink()? Would it set Tea.full
       | to false? Maybe this is obvious to Zig users, but coming from
       | C++, that would be a violation of const correctness.
       | 
       | In C++ (thinking of classes rather than files), you wouldn't be
       | able to call the drink method of a const object because it's not
       | marked as a const method. Or, if it was marked as a const method,
       | you wouldn't be able to modify full from in drink. You could get
       | around this by marking full as mutable, which means you're going
       | to deliberately violate const correctnees, but at least you have
       | to be explicit about it. (In theory mutable is meant for things
       | like caches that don't affect the visible behaviour of the
       | class.)
        
         | kristoff_it wrote:
         | > what if other.zig called Tea.drink()?
         | 
         | To be clear: you would first need to create an instance of
         | `Tea`.                  var t: Tea = .{};        t.drink(); //
         | will work and set t.full to false
         | 
         | If instead we declared `t` as `const`, then yes the call to
         | `drink` would not have been possible.
         | 
         | `const Self = @This();` just binds the top-level struct type
         | definition to a name, which then allows you to refer to it in
         | other plances. Nothing more than that.
        
           | quietbritishjim wrote:
           | Ah that does make sense, thanks.
           | 
           | Is it possible to mark a method const in Zig, like in C++? I
           | guess you set the type of the self parameter to const *Self
           | or similar?
        
             | kristoff_it wrote:
             | Yep!                  fn foo(self: Self) void {}        fn
             | bar(self: *const Self) void {}        fn baz(self: *Self)
             | void {}
             | 
             | In this example, `foo` and `bar` cannot modify `self`,
             | while `baz` can. In the case of `foo`, `self` is passed in
             | by value, but since function arguments are immutable in
             | Zig, it cannot be modified (the compiler might still opt
             | for pass-by-reference under the hood, but the semantics
             | don't change).
             | 
             | In the case of `bar` we are explicitly asking for a
             | constant pointer, as you mentioned.
        
               | MatthiasPortzel wrote:
               | I spent a lot of this weekend learning Zig, and this was
               | the most surprising this for me. foo, bar, and baz are
               | all called the same way (as thing.foo() or thing.baz()),
               | but depending on the type signature, the compiler can
               | figure out whether you want a mutable or immutable
               | reference to the object.
        
               | WalterBright wrote:
               | This is common behavior for languages. D adds another
               | wrinkle to it - a reference can be mutable, const, or
               | immutable.
               | 
               | mutable: can modify it
               | 
               | const: cannot modify it through the reference, but other
               | mutable references can still modify it
               | 
               | immutable: nobody can modify it
               | 
               | The 'immutable' is great for writing threaded code, as no
               | synchronization is necessary.
        
               | masklinn wrote:
               | It's pretty common for languages in the field e.g. C++
               | (const versus non-const methods), Rust (value, unique
               | reference, shared reference, deref), and even Go (value v
               | pointer, delegation).
               | 
               | The "method syntax" is pretty much just a convenience, so
               | why not also handle that after all?
        
         | stefncb wrote:
         | The type of Tea is 'type', not Tea. Unlike in C++, types are
         | values in Zig, so it would be like calling Tea::drink() in C++.
         | 
         | You need an instance of Tea in order to call drink, and if it's
         | const it won't compile because drink takes type *Tea, not
         | *const Tea.
        
           | quietbritishjim wrote:
           | Interesting, thanks. In that case, what if `Tea` was not
           | const? Could I assign a different type to it? What would the
           | static type of an instance of Tea be, if its runtime value
           | could be two incompatible types?
        
             | stefncb wrote:
             | 1. You can use a type variable as a normal one, as long as
             | you do it at compile time (a variable of type type is
             | required to be comptime). You can use a TypeInfo at runtime
             | if you like though.
             | 
             | 2. The type system is deep enough to describe the type of
             | values, but not the type of types. That's what higher
             | kinded types are for, and for better or for worse that's
             | not part of zig. Also, type variables can only exist at
             | compile time, so there is no runtime value.
        
               | quietbritishjim wrote:
               | thanks!
        
       | 0x37 wrote:
       | I don't usually like participating in bikeshedding, but the @
       | annotation feels like PHP's $ to me in that I don't see why it
       | needs to exist. The language design could've easily just left
       | that out and I don't think anything would be lost.
       | 
       | Other than that, I'm definitely excited for Zig as a potential
       | C++ replacement.
        
         | kristoff_it wrote:
         | > I don't see why it needs to exist
         | 
         | The symbol namespaces builtins, as those are the only
         | identifiers that aren't declared in the file (either directly
         | or by using `@import`).
        
           | tialaramex wrote:
           | Although namespacing them keeps them out from under a
           | programmer's feet, which is a significant benefit, it does
           | seem like this would make it harder to find stuff.
           | @cmpxchgStrong, @wasmMemorySize and @embedFile are completely
           | unrelated, but since they're all builtins they're neighbours.
        
             | Conscat wrote:
             | This is sort of an issue we already deal with in other
             | languages, and imo it's not a huge deal in those.
             | Personally I find @ more reasonable than __builtin_
        
         | tleb_ wrote:
         | My belief is that it is about builtin functions that are
         | provided by the compiler versus part of the standard library.
         | They are documented in the language reference [0] versus in the
         | standard library documentation [1].
         | 
         | [0]: https://ziglang.org/documentation/master/
         | 
         | [1]: https://ziglang.org/documentation/master/std/
        
         | ptato wrote:
         | Built-in names are essentially reserved words, and there are
         | dozens of them. The @ prefix ensures you don't step on user's
         | variable names, and that you can add new built-ins without
         | making breaking changes.
        
           | andrepd wrote:
           | Why aren't they simply namespaced, like `core.some_function`?
        
             | nine_k wrote:
             | 5 characters to type instead of 1.
        
             | AnIdiotOnTheNet wrote:
             | Because there'd need to be a magical exception for the
             | "core" namespace that makes it not just a file of Zig code
             | somewhere like every other module.
        
               | [deleted]
        
               | hiccuphippo wrote:
               | Isn't there already a magical exception for "root" and
               | "builtin"?
        
               | kristoff_it wrote:
               | not really, those are two modules that are always
               | available to you, but you still have to import them like
               | any other Zig module `const builtin =
               | @import("builtin");`
        
         | throwawaymaths wrote:
         | one side benefit is that at lot of @ code is "dangerous shit"
         | so it draws your eye during code review. You will want to code
         | review the "dangerous shit" that GPT-5 gives you.
        
       | ptato wrote:
       | Please don't do "const Self = @This();" if the type isn't
       | generic. It is pointless, redundant, ugly.
       | 
       | What issues could public struct fields have caused?
        
         | latch wrote:
         | Self is used in the standard library for non generic types
         | every now and again.
         | 
         | As for public fields, the 0.9 change to allocator comes to
         | mind. This was written off as a minor change, because Zig is
         | pre 1.0. But what I haven't heard (and 100% upfront, I haven't
         | looked hard), is how Zig plans on dealing with it post 1.0.
         | Like, where are we in the gradient of: "1.0 will never break
         | the public API (struct fields included)" to "deal with it".
        
           | kristoff_it wrote:
           | 1.0 for Zig is going to mean not only never break the API,
           | but also that we'll be pretty much done with the language.
           | The current idea is to release v1 only after a few releases
           | that only contain bug fixes.
           | 
           | > As for public fields, the 0.9 change to allocator comes to
           | mind.
           | 
           | That's a good example of why it makes sense for Zig to have
           | all fields be public: the change that we did to allocators
           | has important implications that in Zig we don't want to hide
           | behind a `private` descriptor. This is obviously not a
           | universal truth, but it makes sense for a low-level
           | programming language that cares about the details.
           | 
           | > Self is used in the standard library for non generic types
           | every now and again.
           | 
           | I agree with the parent poster, those should probably be
           | removed, as they don't really help make the code more
           | readable or provide any other advantage. A overhaul of the
           | stdlib is planned, but we're not there yet, as we're still
           | busy working on the package manager and incremental
           | compilation.
        
             | hellcow wrote:
             | I'd like to chime in on the all-fields-are-public debate.
             | 
             | If I'm a library author and release something which exports
             | a struct... I can never change any of the struct's internal
             | data structures? I can never rename a field. Or delete a
             | field. Or change what should be an internal field's type.
             | And that makes non-breaking changes as a library author
             | much more challenging, which may be a very serious problem
             | for Zig, especially if the stdlib isn't Go-style "batteries
             | included". I would seemingly need to version the structs to
             | work around this: ArrayListV1, ArrayListV2...
             | 
             | Part of the beauty of great code is in its API design. And
             | forcing my libs to document:
             | 
             | /// Don't touch this field.
             | 
             | Without any way for me to hide the field itself from users
             | or prevent them from using it is very strange. Users can't
             | intuit what to use and not use without studying
             | documentation, which a well-thought out API might improve.
             | 
             | Go has been extremely successful in part due to its nicely
             | thought out APIs in the stdlib. They have certain fields
             | which anyone would care to use, and all the rest are--for
             | your purpose as a user--not there. And this same design has
             | made refactors of libraries/packages (without breaking
             | changes for users) very easy.
        
               | kps wrote:
               | When I first played with Zig (and I haven't done more
               | than play), I had a similar view. I suspect that the
               | adoption of Zig in certain current C niches may be
               | hampered by the inability to express things like the
               | opaque pointer idiom, mandated by coding standards like
               | CERT C and MISRA[ble] C.
               | 
               | I now think that an external tool is the way to go. C++
               | has field access control, but for some projects, that's
               | not enough, so they have an external layer of access
               | control, like Chromium DEPS (sample: https://source.chrom
               | ium.org/chromium/chromium/src/+/main:ui/... )
               | 
               | And use cases of libraries are different. Perhaps most
               | uses of library X use it as a black box and should never
               | directly access structure fields, but project X'
               | integrates closely with X. That's hard to express in the
               | language since it's hard to express purely within X at
               | all. Maybe you need a `foo.@i_know_this_is_private_but_le
               | t_me_use_it_anyway(bar)`, like Python's underscore
               | convention.
               | 
               | Or you have an external static analyzer using some sort
               | of allow and deny rules. Maybe library X declares `Deny
               | *` but project X' overrides it with an `Allow X.foo.bar`.
               | The rules can be explicit, automatically enforced, and
               | subject to code review, so it's OK.
        
               | bsder wrote:
               | > If I'm a library author and release something which
               | exports a struct... I can never change any of the
               | struct's internal data structures?
               | 
               | Stucts in most modern languages are _NOT_ guaranteed to
               | have any particular layout unless you explicitly ask for
               | it--so you don 't have that guarantee to begin with. This
               | enables them to rearrange the struct for better packing
               | or AoS/SoA transformations.
               | 
               | If someone grovels in your structs and gets burned, well,
               | that's their own fault. I'll go further, if someone
               | grovels in a library struct, they have failed. I'll go
               | even further, if someone _has_ to grovel in a library
               | struct, the _library_ has failed.
               | 
               | In addition, in embedded contexts I _regularly_ have to
               | grovel in structs to work out some bug.  "static" is
               | often the bane of my existence in C because compilers
               | often throw away or hide references to those variables.
               | However, they may be crucial to running down state.
        
               | kristoff_it wrote:
               | It's impossible to state universally what should or
               | should not be considered part of the public api of a
               | struct when it comes to low-level programming.
               | 
               | In some cases the exact layout of the struct needs to
               | stay stable. In others the contents don't matter, only
               | that the size doesn't change. In others still, none of
               | those properties matter and it's just a matter of keeping
               | names and types stable, and that every new field that
               | gets added has a default value so that old initialization
               | expressions keep working.
               | 
               | This is just to name a few cases among many others. In
               | the end you will need to specify in the docs what it is
               | that you, the library author, want to consider part of
               | the public API and what is considered an implementation
               | detail.
               | 
               | Private fields could help with some of that, sure, but
               | there are also big downsides. Andrew gave a while ago a
               | good overview on the more "ziggy" perspective on the
               | issue: https://github.com/ziglang/zig/issues/9909#issueco
               | mment-9426...
        
               | hellcow wrote:
               | Awesome, I will do some more digging into this. Thanks
               | for the thoughtful reply!
        
               | throwawaymaths wrote:
               | I work in a language where all fields are public and it
               | is indispensable in debugging things. You DO NOT want
               | your datatypes hiding things under the hood.
               | 
               | At best, if zig does want to make fields private, they
               | should still be visible, but the compiler should disallow
               | writing to them.
        
               | hellcow wrote:
               | I believe you, but I haven't encountered anything like
               | this in my work in a higher-level language, and this is
               | my first push into manual memory management since I
               | studied C in college many years ago.
               | 
               | Can you offer some examples of times when it has been
               | indispensable, so that I can understand when this is
               | useful?
        
               | throwawaymaths wrote:
               | don't remember any off the top of my head. I use elixir,
               | I do often dive into the data structures with
               | IO.inspect(..., structs: false) which exposes all of the
               | "hidden" fields. Enough so that I know how to do this
               | without having to look it up.
               | 
               | I suppose you could also use the debugger, but I value my
               | time.
        
       | haberman wrote:
       | > Speaking of structure fields, they're always public. Structures
       | and functions are private by default with an option to make them
       | public. But struct fields can only be public.
       | 
       | This feels like a mistake to me. I own and maintain a C library,
       | and lack of private struct members is one of the things that
       | causes the most problems. My users frequently reach into struct
       | members even though I really do not want them to. This causes
       | problems when those members have subtly different semantics than
       | what they expect. It's even worse when I want to change the
       | internal representation, and I have to track down all my users'
       | code and change it accordingly.
       | 
       | In C, my only options are:
       | 
       | (1) Define the structure in a .c file instead of .h. This works
       | great in cases where I can take the performance hit of not being
       | able to inline accesses of those struct members. But for
       | performance-critical structures where I need to be able to
       | inline, this is not an option.
       | 
       | (2) Make the member names something like private_foo,
       | internal_foo, etc. and hope my users take the hint. But this also
       | makes my own code more ugly and creates longer lines that are
       | more likely to wrap.
       | 
       | Unfortunately Zig is even less capable than C in this regard,
       | because Zig takes away option (1). Since there is no
       | header/source split in Zig, I cannot make a struct opaque by
       | defining it in a source file only. Though I do see that there is
       | "opaque {}", perhaps that plus some casting could accomplish a
       | similar thing?
       | 
       | I see that rationale for not having private struct members was
       | given here:
       | https://github.com/ziglang/zig/issues/9909#issuecomment-9426...
       | 
       | > The idea of private fields and getter/setter methods was
       | popularized by Java, but it is an anti-pattern. Fields are there;
       | they exist. They are the data that underpins any abstraction. My
       | recommendation is to name fields carefully and leave them as part
       | of the public API, carefully documenting what they do. [...] In
       | my subjective experience, public fields generally lead to better
       | abstractions by eliminating the temptation to attempt full
       | encapsulation, when the more effective strategy is to provide
       | composable abstraction layers.
       | 
       | Java does indeed represent an anti-pattern of verbose and
       | frequently trivial getters and setters. But newer languages like
       | C#, Swift, and Dart have more elegant and low-overhead syntax for
       | properties, even allowing a property to move between being an
       | actual field member and being derived, without breaking users.
       | 
       | As a library maintainer, full encapsulation is very important for
       | evolution of a system over time. The only way for layers to truly
       | be layers is if the contract at each layer boundary is clear.
       | Otherwise the layers gel together and you cannot safely change
       | any one layer independently.
        
         | hota_mazi wrote:
         | > Java does indeed represent an anti-pattern of verbose and
         | frequently trivial getters and setters.
         | 
         | It's not an anti-pattern.
         | 
         | This is how Java provides the exact functionality that you
         | describe as very important (field encapsulation). Because Java
         | doesn't support properties, it needs to go through getters and
         | setters.
         | 
         | As you point out, more modern languages support this
         | functionality directly in the language with properties.
        
           | haberman wrote:
           | To me the anti-pattern is lots of redundant, trivial, verbose
           | getters and setters. If I see a source file with tens or
           | hundreds of lines of trivial method definitions, I think
           | something has gone wrong.
           | 
           | Properties help avoid this anti-pattern by making it possible
           | to transparently swap in a computed property for what used to
           | be a plain field. This removes the incentive to proactively
           | create trivial getters and setters "just in case."
        
             | WalterBright wrote:
             | > a source file with tens or hundreds of lines of trivial
             | method definitions
             | 
             | Something has indeed gone wrong. The struct has succumbed
             | to being a "kitchen sink".
        
               | tialaramex wrote:
               | Let's take something like C++ std::vector / Rust's Vec /
               | Zig ArrayList
               | 
               | Although there's some serious meat in a few places (e.g
               | the growth amortization and associated mechanisms) many
               | of the methods defined for these types are trivial.
               | 
               | In C++ they don't _look_ trivial because of layers of
               | pre-processor cruft, terrible lack of language hygiene,
               | and the perverse insistence that today 's code should
               | compile correctly with a compiler that's as old as Shrek
               | (the movie that is).
               | 
               | But they are, ultimately, still trivial, even if writing
               | a *= 2; takes sixteen lines of macros and a dozen
               | structure tricks to ensure that compiler doesn't think we
               | meant the global variable a, or the structural type a, or
               | the function named a, or a dozen other things we couldn't
               | possibly know about because this language is very, very
               | stupid.
               | 
               | Here's Vec's implementation of a commonly used method:
               | pub fn is_empty(&self) -> bool {             self.len()
               | == 0         }
               | 
               | Here's Zig's implementation of another commonly used
               | method:                       pub fn pop(self: *Self) T {
               | const val = self.items[self.items.len - 1];
               | self.items.len -= 1;                 return val;
               | }
               | 
               | I expect if I poke around in D I'll find similar,
               | although the last time I did that I found a bug, and the
               | experience wasn't fun so I think I'll pass.*
        
         | jcelerier wrote:
         | > My users frequently reach into struct members even though I
         | really do not want them to.
         | 
         | if as a library writer you don't want me to access your private
         | struct layout and as a user I want to, I promise there is
         | absolutely nothing that I won't try, from casting to bytes and
         | working there, passing things from / to DLL writtens in C, to
         | running your program in a VM, etc. So why try to fight this
         | fight? Just tell your users that go outside of the sanctioned
         | route that you won't help them just like a car manufacturer
         | won't help you hack into their engine but you still can and
         | should be able to if you want to
        
         | [deleted]
        
         | michaelg7x wrote:
         | What are the downsides of keeping the public fields in a struct
         | which is in turn embedded in a private struct created by the
         | library? Inferring the "outer offset" could get irritating but
         | it depends on your tolerance of additional complexity (in every
         | public function...).
        
           | haberman wrote:
           | > What are the downsides of keeping the public fields in a
           | struct which is in turn embedded in a private struct created
           | by the library?
           | 
           | That seems similar to using "opaque {}", if I am
           | understanding you correctly?
           | 
           | I think either of those solutions could potentially work, at
           | the cost of one explicit cast in each function that actually
           | needs to access the data. I would love to know if this works
           | well enough in practice.
        
           | JonChesterfield wrote:
           | The answer in C would be it constrains ordering/packing of
           | fields, so you'd end up with N public/private wrappers and
           | generally a lot of line noise, but I have a vague
           | recollection that zig reorders fields as it sees fit anyway
           | making that a non-issue
        
         | WalterBright wrote:
         | D has a more unusual take on private fields. Private fields
         | prevent access by other modules, but do not prevent access from
         | within the same module. The module is really the unit of
         | encapsulation, not the struct.
         | 
         | The purpose of this was to eliminate the need for "friend"
         | classes.
         | 
         | This sometimes conflicts with what people coming from C and C++
         | are used to with everything being in one file (after headers
         | are added by the preprocessor). But having modules gives
         | opportunities for better ways to organize the code.
         | 
         | As for trivial property accessors and setters, those get
         | inlined, so are not overhead.
        
           | justeleblanc wrote:
           | > D has a more unusual take on private fields. Private fields
           | prevent access by other modules, but do not prevent access
           | from within the same module. The module is really the unit of
           | encapsulation, not the struct.
           | 
           | Other languages would call these "internal", not "private".
        
             | LegionMammal978 wrote:
             | Rust uses "private" to express the same notion, that a
             | field can be accessed from the defining module and its
             | submodules, but not from any other modules, unless you
             | explicitly add a visibility modifier like "pub" or
             | "pub(crate)".
        
         | TUSF wrote:
         | Zig compiles everything as one large compilation unit, so
         | inlining isn't much of an issue there. If you want to have
         | private fields, there's a couple ways to go about it. You can
         | make use of opaque types, of course, or you could use
         | @fieldParentPtr to hide your private implementation from the
         | API.
         | 
         | Something like so:                   /// The internal
         | implementation of this type.         const PrivateType = struct
         | {             // Private fields.             field_a: u32,
         | field_b: bool,             // Public object exposed
         | public: PublicType,                  // private functions here.
         | }              /// This gets exposed to the public API
         | pub const PublicType = struct {             // Public fields.
         | field_a: u32,                  // Public functions here.
         | pub fn create(alloc: Allocator) !*PublicType {
         | var priv = try alloc.create(PrivateType);
         | return &priv.public;             }                  /// Flips
         | internal field b.             pub fn flip(p: *PublicType) void
         | {                 // Grabs the PrivateType that contains this
         | PublicType as a field.                 const self =
         | @fieldParentPtr(PrivateType, "public", p);
         | self.*.field_b = !self.*.field_b;             }         }
        
         | scrubs wrote:
         | For my interests (DPDK, NIC, low level storage I/O etc.) I have
         | high hopes for Zig. And for reasons other have explicated over
         | the last year on HN, I think it'll work much better than Rust.
         | 
         | Readers should have realistic expectations. It's not
         | substantially downhill to write C like code. I ran into this
         | bug right off the bat. TL/DL: Zig's linker does not pull in
         | dynamic libs. It finds static libs but alas the .tsk doesn't
         | give expected behavior anyway:
         | 
         | https://github.com/ziglang/zig/issues/14939
         | 
         | A. Kelly (head Zig dev), to his credit, seems to already be on
         | top of in this through a related bug:
         | 
         | https://github.com/ziglang/zig/issues/14963
         | 
         | I didn't expect to run into link problems on day 1.
        
       | ngrilly wrote:
       | Regarding naming conventions, there is an issue about switching
       | to snake case for functions as well, the main rationale being
       | there is no difference between snake_case and camelCase for one
       | word identifiers:
       | 
       | https://github.com/ziglang/zig/issues/1097
        
         | kuon wrote:
         | I have PTSD about 1097 :D
        
           | overthrow wrote:
           | Then you better refill your Zoloft because it looks like
           | there's going to be one last big disruption before it's over.
           | https://github.com/ziglang/zig/issues/1097#issuecomment-1404.
           | ..
        
       | the_mitsuhiko wrote:
       | > The Zig documentation states that "Identifiers are never
       | allowed to "hide" other identifiers by using the same name."
       | 
       | I see this desire to get rid of shadowing all the time, but in
       | practice it's such a disruptive restriction.
        
         | WalterBright wrote:
         | I added shadowing protection to D a couple decades ago. It has
         | saved me from numerous difficult-to-find bugs, especially if
         | the function gets a bit large. It's a big win.
        
         | [deleted]
        
         | fractalb wrote:
         | Maybe, they'll remove it in a future version if many people
         | raise the same complaint.
        
           | throwawaymaths wrote:
           | they'll never remove it, because not shadowing variables
           | really encourages you to break up your code into well-
           | organized files. IMO, it's only painful if you
           | 
           | 1) have bad coding habits. (playing with zig broke me of some
           | of these)
           | 
           | or
           | 
           | 2) you need to autogenerate code.
           | 
           | 2 is a much harder nut to crack.
        
         | fatneckbeard wrote:
         | two hardest problems in programming
         | 
         | what to name things
         | 
         | cache coherency
         | 
         | off by one bugs
        
         | AnIdiotOnTheNet wrote:
         | You make it sound so difficult, but in my experience it hardly
         | ever comes up in practice. When it does, rather than naming
         | things obscurely I can just tack a temp_ or local_ or something
         | else descriptive on the front.
        
           | jeltz wrote:
           | When I used Zig I ran into this all the time in practice when
           | using iterators. It is the combination of that you cannot
           | nicely create a locally scoped iterator and that Zig forbids
           | shadowing.
           | 
           | Specifically this kind of code (and I do not feel calling
           | them e.g. iter1 and iter2 or foo_iter and bar_iter make the
           | code nice):                   var iter1 = foo_dir.iterate();
           | while (try iter1.next()) |entry| {             // Do
           | something         }              var iter2 =
           | bar_dir.iterate();         while (try iter2.next()) |entry| {
           | // Do something else         }
        
             | messe wrote:
             | At the moment, the recommended solution for that is to use
             | a block scope.                   {             var iter =
             | foo_dir.iterate();             while (try iter.next())
             | |entry| {                 // Do something             }
             | }              {             var iter = bar_dir.iterate();
             | while (try iter.next()) |entry| {                 // Do
             | something             }         }
        
             | AnIdiotOnTheNet wrote:
             | Being clear about things is one of Zig's central
             | philosophies, if naming things `foo_iter` and `bar_iter`
             | doesn't feel like the right solution then maybe the
             | language isn't a good fit for you.
             | 
             | But also:                   var iter = foo_dir.iterate();
             | while (try iter.next()) |entry| {             // Do
             | something         }              iter = bar_dir.iterate();
             | while (try iter.next()) |entry| {             // Do
             | something else         }
             | 
             | You can totally just reassign `iter` here since you're done
             | with the first iterator (assuming they are both the same
             | type, which appears to be the case).
        
               | jeltz wrote:
               | I do not think long variable names is very clear. What is
               | clear is locally scoped ones and there I feel Zig has
               | some deficiencies which makes it unnecessarily cumbersome
               | to have locally scoped iterators.
        
         | sophiabits wrote:
         | The very last paragraph sums it up pretty nicely imo:
         | 
         | > You'll have to be more creative when coming up with variables
         | that don't shadow existing ones (which, for me, generally means
         | using more obscure names).
         | 
         | The last adjective I want used in relation to my variable names
         | is "obscure."
        
           | jackosdev wrote:
           | I really like in Rust how you can reinit a variable with a
           | different type e.g. "let rect: Rect<f32> = rect.into();"
           | 
           | It's just so damn useful and I'm not sure what the downside
           | is, it sucks when you have to keep coming up with different
           | names so you can keep around an identifier that you don't
           | need anymore.
        
             | ithkuil wrote:
             | there are things that make perfect sense when a language
             | forces you to use an IDE anyway if you want to do anything
             | longer than a toy.
             | 
             | Shadowing is not a big deal with IDEs; you can always see
             | the type of the variable , jump to definition easily etc
             | etc.
             | 
             | The rule to not shadow variables makes more sense when you
             | want to understand the code just by looking at it.
        
               | layer8 wrote:
               | With shadowing, you can use or mutate a variable,
               | thinking you are using/mutating the outer instance
               | because you're unaware of the inner (shadowing) instance,
               | which is the one you are really using/mutating. An IDE
               | doesn't help catching such an inadvertent error (unless
               | it warns about shadowing variables, but then you'd want
               | to rename it anyway, to get rid of the warning).
               | 
               | I've tripped over unexpected shadowing often enough that
               | I wish more languages would forbid it. I rarely have
               | trouble choosing appropriate variable names to avoid
               | shadowing.
        
               | ithkuil wrote:
               | it's a footgun indeed and no IDE per se doesn't solve all
               | the problems. But since rust was mentioned, there are
               | other rust features that make that less of a problem:
               | most of the rust code uses immutable variables and only
               | rarely you do use mut variables and mut references and
               | these can be under bigger scrutiny by reviews and
               | linters.
               | 
               | I focused on IDEs in my comment because I find shadowing
               | to be a problem _even with_ immutable variables, because
               | it 's hard for you to tell what is the type of a variable
               | if it keeps change throughout the function body.
        
             | signaru wrote:
             | At the opposite end of the spectrum, there are languages
             | with case insensitivity and even style insensitivity. I
             | personally avoid them, but it's interesting how the users
             | of these languages have a very different philosophy.
        
               | FpUser wrote:
               | I use C++ and Delphi / Lazarus. I guess I have a "very
               | different philosophy" then I ;)
               | 
               | To me either has pros and cons.
        
             | alpaca128 wrote:
             | > I'm not sure what the downside is
             | 
             | The downside is that you may get a weird bug and only after
             | a while see that you accidentally overwrote a function
             | parameter and the Rust compiler didn't even warn you about
             | it.
             | 
             | For this reason I always add the following line to my
             | projects to enable warnings:
             | #![warn(clippy::shadow_reuse, clippy::shadow_same,
             | clippy::shadow_unrelated)]
             | 
             | You can also use "deny" instead of "warn" to make it an
             | error. I also like "#![deny(unreachable_patterns)]", which
             | detects bugs in enum pattern matching if you accidentally
             | match "Foo" instead of "Type::Foo" - I honestly don't know
             | why this isn't set by default.
        
               | stouset wrote:
               | > you accidentally overwrote a function parameter
               | 
               | To "accidentally" overwrite it, you have to either:
               | a) explicitly mark the parameter binding as mutable: fn
               | foo(mut bar: T)       b) explicitly re-bind the variable
               | with let (let bar: T = ...)
        
               | masklinn wrote:
               | > The downside is that you may get a weird bug and only
               | after a while see that you accidentally overwrote a
               | function parameter and the Rust compiler didn't even warn
               | you about it.
               | 
               | If you "overwrite" a function parameter without using it,
               | the compiler will warn you of an unused variable.
               | 
               | If you "overwrite" a function parameter because you're
               | converting it, it's a major use case of the feature.
               | 
               | > I honestly don't know why this isn't set by default.
               | 
               | Because the author of the match can't necessarily have
               | that info e.g. if you match on `Result<A, B>` but `B` is
               | an uninhabited type (e.g. Infallible), should the code
               | fail to compile? That would make 95% of the Result API
               | not work in those cases. Any enum manipulating generic
               | types could face that issue.
               | 
               | IIRC it was originally a hard error, and was downgraded
               | because there were several edge cases where compilation
               | failed either on valid code, or on code which was not
               | fixable (for reasons like the above).
        
               | alpaca128 wrote:
               | > If you "overwrite" a function parameter because you're
               | converting it, it's a major use case of the feature.
               | 
               | Or it's unintended and thus a bug. I personally almost
               | never intentionally shadow variables so I turned it into
               | warnings.
               | 
               | > e.g. if you match on `Result<A, B>` but `B` is an
               | uninhabited type (e.g. Infallible), should the code fail
               | to compile?
               | 
               | This specific example you chose is probably the least
               | relevant here, as the Result type doesn't require you to
               | write "Result::Err(_)" instead of just "Err(_)", both
               | will correctly match. Which can of course also be done
               | for custom enums by "importing" their variants ("use
               | EnumName::*;"). But in my experience it's easy to
               | accidentally omit the type in the match pattern and then
               | suddenly it matches everything. I personally can't
               | imagine a situation where this is intentional and have
               | spent way too much time debugging this specific issue,
               | hence I choose to turn it into an error.
        
               | the_mitsuhiko wrote:
               | > The downside is that you may get a weird bug and only
               | after a while see that you accidentally overwrote a
               | function parameter and the Rust compiler didn't even warn
               | you about it.
               | 
               | It will absolutely warn about this:                   fn
               | foo(i: u32) -> u32 {             let i = 42;
               | i         }              fn main() {
               | dbg!(foo(42));         }
               | 
               | results in                   warning: unused variable:
               | `i`          --> src/main.rs:1:8           |         1 |
               | fn foo(i: u32) -> u32 {           |        ^ help: if
               | this is intentional, prefix it with an underscore: `_i`
               | |           = note: `#[warn(unused_variables)]` on by
               | default
        
               | alpaca128 wrote:
               | I don't know what the difference was, but 1-2 years ago
               | it definitely did not warn me. Perhaps it doesn't show a
               | warning when you assign a different datatype?
        
               | kibwen wrote:
               | The unused variable warning has been around since before
               | 1.0, and works even when the types of the variables are
               | different.
        
               | estebank wrote:
               | There's only one case where shadowing has bitten me in
               | the past: long methods with loops dealing usize almost
               | exclusively, where shadowing external bindings inside the
               | loop might make sense, but any mistake would be silent.
               | This was in the context of terminal layout code. The
               | solution there has been extensive testing, but what I
               | should have done is split the megafunction into multiple
               | smaller ones.
        
             | andrepd wrote:
             | Well why not with the same type? Sounds like an arbitrary
             | restriction: you can use this idiom, but only sometimes.
        
               | alpaca128 wrote:
               | Why would you do that with the same type instead of just
               | making the variable mutable? And you can do it, I just
               | don't think it's a good idea as you now effectively have
               | a mutable variable without it being marked as such.
        
               | eru wrote:
               | No, it's still better than a mutable variable. Because
               | it's not a mutable variable, just a series of variables
               | that happen to have the same name.
               | 
               | Mutable state is 'evil' and makes your program harder to
               | reason about on a semantic level. Shadowing is merely a
               | syntactic choice with pros and cons.
               | 
               | I like shadowing in Rust, it works well there. In eg
               | Python or Haskell, it works less well, but for different
               | reasons. (In Haskell it's because of laziness and
               | definitions being co-recursive by default. In Python it's
               | because the language doesn't give you any tools to tell
               | apart assignment to an existing variable from creation of
               | a new variable.)
        
               | alpaca128 wrote:
               | > it's not a mutable variable, just a series of variables
               | that happen to have the same name.
               | 
               | Fair point, though in that case I'd be more comfortable
               | separating those variables into scopes.
               | 
               | > Mutable state is 'evil' and makes your program harder
               | to reason about on a semantic level. Shadowing is merely
               | a syntactic choice with pros and cons.
               | 
               | Both result in multiple states of the same identifier, so
               | I don't quite see the big difference here. In Rust I
               | already have the clearly visible "mut" keyword telling me
               | that it'll be overwritten.
        
               | tialaramex wrote:
               | There is no such restriction, it's just much less common
               | to want that.                 let x: u32 = 5;       let
               | x: u32 = 10; // You can write this, but why?       let x:
               | u32 = 20; // I really feel like you should re-consider
               | 
               | If you end up shadowing this way in a long function it
               | more likely means the function got too long. On the other
               | hand, I certainly have had cause to shadow variables in
               | inner scopes e.g.                 let x =
               | some_complicate_stuff();       for dx in [-1, 0, 1] {
               | let x = x + dx;         // Do stuff with x very naturally
               | here, rather than keep saying "x + dx" everywhere       }
               | // But outside the loop x is just x, it's not x + dx
        
             | tialaramex wrote:
             | This is idiomatic Rust, it works very nicely there, however
             | most languages aren't Rust
             | 
             | Rust's Into::into() is consuming the object in the old (now
             | shadowed) rect variable. So conveniently the old rect
             | variable which we can't access also no longer has a value+.
             | In many languages a method can't consume the object like
             | that, so the old object still exists but we can't access it
             | because it is shadowed.
             | 
             | For example in C++ they have move semantics, but their move
             | isn't destructive, so the object is typically hollowed out,
             | but still exists until the end of the scope at least.
             | 
             | Rust's type strictness matters here too. It means if you
             | later modify some code using rect meaning whatever it was
             | _before_ that statement morphing it into a Rect <f32>
             | chances are it doesn't type check and is rejected. For
             | example in many languages if (rect) { ... } would be legal
             | code and might change meaning as a result of the
             | transformation, but in Rust only booleans are true or
             | false.
             | 
             | + Unless this previous variable's type implemented the Copy
             | trait and therefore it has Copy semantics and consuming it
             | doesn't do anything.
        
               | mr_00ff00 wrote:
               | This is interesting to me that C++ allows you to access a
               | value after move is called. Presumably it wouldn't be
               | hard for the compiler to yell at you.
               | 
               | I assume accessing it is undefined behavior?
               | 
               | I would assume you could change this without affecting
               | backwards compatibility.
        
               | mindv0rtex wrote:
               | The only requirement placed on the "moved out" variable
               | is that you should be able to call its destructor. Which
               | means that it has to be in a valid but unspecified state.
               | So it's fine to access such a variable, so long as you
               | don't read its exact state. You can still assign to it,
               | for instance.
        
               | tialaramex wrote:
               | As another commenter says the moved-from object should
               | have "Valid but unspecified state" (types provided by the
               | standard library will do that, custom types merely
               | _should_ do that)
               | 
               | Since you don't know _what_ valid state it has, calls
               | with pre-requisites are nonsense (e.g. if you have a Bird
               | and the method land requires that the Bird should be
               | flying, you can 't call land() on a moved from Bird,
               | because you don't know if it's flying) but all calls
               | without pre-requisites are fine e.g. asking how long a
               | string you moved is would work - it's probably zero
               | length now, but maybe not.
               | 
               | > Presumably it wouldn't be hard for the compiler to yell
               | at you.
               | 
               | In the general case this is Undecidable, so, the opposite
               | of not hard.
               | 
               | > I would assume you could change this without affecting
               | backwards compatibility.
               | 
               | C++ which relies on this exists today, the most likely
               | path to actually landing destructive move in C++ would be
               | to add a whole _new_ set of construction and assignment
               | operators for destructive move, forcing people to opt in
               | and adding to the many sets C++ already has, and likely
               | angering C++ developers a great deal in the process.
               | 
               | Howard Hinnant, whose design today's non-destructive move
               | is, did argue that in principle it's possible to add
               | destructive move to the language later if desired, but
               | his description rather undersells the benefits of this
               | design, presumably because he couldn't deliver it. Maybe
               | he'd watched enough Mad Men (yup, Mad Men's early seasons
               | pre-date C++ having move semantics) to know you shouldn't
               | tell the customer what they can't have or they'll want
               | it.
               | 
               | Common things to actually do with a C++ variable after
               | moving from it are:
               | 
               | * Nothing, but in the knowledge it won't be cleaned up
               | until the scope ends * Re-assign it, destroying the
               | hollowed out object immediately * Re-use the hollowed out
               | object, e.g. call a clear() method on it and then use as
               | normal
        
             | sophiabits wrote:
             | Exactly! I was thinking of shadowing in Rust when I wrote
             | my original comment.
             | 
             | My day job is predominantly in Typescript and a lot of code
             | winds up reading significantly worse than it needs to. A
             | common pattern for me is unique-ifying some sort of
             | array--"const dataUnique = new Set(data);" is horrible, and
             | if there's no reason to keep the original "data" variable
             | in scope then it's doubly bad; I want to keep as little
             | context in my head as possible.
        
             | cillian64 wrote:
             | The downside is when reading code you're keeping in your
             | head information about the type of each variable. If you
             | skim through the code and miss one of these redefinitions
             | then you may be mistaken about the variable's type.
             | 
             | That said, I still think sparing use of this is justified,
             | especially with an editor which can show types on
             | mouseover.
        
               | jackosdev wrote:
               | That's true, but this has never been a problem for me
               | looking through large codebases and doing code reviews,
               | in other languages I was constantly annoyed by not being
               | being able to shadow
        
             | masklinn wrote:
             | Definitely super useful, especially in a language where
             | such conversions are rather common.
             | 
             | Also useful because you can't have abstracted local types,
             | so let's say you're building an iterator in a language with
             | interfaces you could do something like
             | let it: Iterator = some().thing();         // intermediate
             | stuff         it = it.some().transform();         // more
             | intermediate stuff         it = it.final().transform();
             | 
             | But in Rust that won't work, every adapter yields a
             | different concrete type, you'd have to box every layer to
             | make them compatible. Intra-scope shadowing solves that
             | issue.
             | 
             | The biggest downside is that it's possible to reuse names
             | for completely unrelated purposes, which can make code much
             | harder to understand. Clippy has a shadow_unrelated lint
             | but it's allowed by default because it's a bit limited.
        
               | eptcyka wrote:
               | You could just create new bindings for each new `it`,
               | `let it = ...; let it = it.too();`
        
               | masklinn wrote:
               | That's the point, you can because rust supports intra-
               | scope shadowing.
               | 
               | If it didn't you'd have to type-erase, or create a new
               | independently-named binding for every step, as you do in
               | e.g. Erlang (can't say this is / was my favourite feature
               | of the langage).
        
               | Joker_vD wrote:
               | Yes, the fact that "V = expression" means "if variable V
               | doesn't exist, assign expression's value to it; otherwise
               | compare the expression's value with the value of V and
               | raise exception if they're not equal" is one of my least
               | favourite parts of Erlang.
               | 
               | I semi-regularly introduce local variables named exactly
               | like one of the function's parameter and then spend
               | several minutes trying to understand why the line
               | expression on the right-hand side of assignment throws
               | badmatch: of course, it doesn't, it's the assignment
               | itself that throws it.
        
               | masklinn wrote:
               | Yes that's also an interesting facet of the language.
               | IIRC it makes sense because of the Prolog ancestry, so it
               | kinda-sorta looks like unification if you squint, but boy
               | is it annoying.
        
           | throwawaymaths wrote:
           | that's a super weird reflex. For me, I would probably wind up
           | making a _more specific_ variable name, instead of obscuring.
        
         | Wildgoose wrote:
         | Don't forget that zig files are essentially a struct using the
         | same name as the file. That means you can differentiate between
         | identifiers that are using the same name by supplying the
         | struct name as a prefix.
        
       | nromiun wrote:
       | > Functions are camelCase
       | 
       | > Types are PascalCase
       | 
       | > Variables are lowercase_with_underscores
       | 
       | Just why? It does not seem like the same language that used to
       | treat tabs as a compiler error. Looks like the worst of every
       | worlds to me. Why not just stick to one style?
        
         | claytongulick wrote:
         | It's helpful when reading code.
         | 
         | I actually use this approach in my style guides for my teams
         | writing js too.
         | 
         | Once you get used to it, it's quite pleasant.
         | 
         | In js you run into some grey areas, like function type callable
         | variables that look weird, but still make sense when named
         | well: click_handler() read_complete_callback()
        
         | randyrand wrote:
         | I've written 100k lines of C code the past 2 years, and this is
         | also the convention I use.
         | 
         | It's quite nice to work with, and comes naturally after a short
         | while.
        
         | TeaDude wrote:
         | Did they remove that? I thought that tabs were still
         | forbidden...
        
           | nromiun wrote:
           | https://github.com/ziglang/zig/issues/544
           | 
           | Maybe? They say their stage2 parser accepts them now.
        
         | generichuman wrote:
         | I don't see what's the problem here. That style has the
         | advantage of being able to look at any identifier and
         | understand whether it is an fn, a type or a variable.
        
           | canadianfella wrote:
           | [dead]
        
           | zamalek wrote:
           | Like int_age and string_name make the type of a value
           | obvious? Hard pass.
        
           | nromiun wrote:
           | Isn't fn and () already doing that for functions? Anyway, I
           | just think it is a weird style for a minimalist language.
           | 
           | Edit: Looks like they are already aware of the problem. That
           | is good news.
           | 
           | https://github.com/ziglang/zig/issues/1097#issuecomment-6142.
           | ..
        
         | throwawaymaths wrote:
         | it's a convention, not enforced. You can do whatever you want
         | with names. These are just guidelines.
        
       | [deleted]
        
       | scoutt wrote:
       | Coming from C, I always see these "Self" variables/types as
       | counter-intuitive, and contribute to those cases where a function
       | requires a parameter which I don't have to supply (just for these
       | functions accepting "Self"), which could be an exception instead
       | of a rule. I would rather prefer the C++ "this" keyword usage.
       | 
       | Also, do "Self" kind-of variables occupy memory?
       | 
       | How can I declare a (packed) struct describing a payload in a way
       | that the struct can be then sent "as is" as a network packet?
       | 
       | Let's say this struct has a "calculateChecksum()" method. Will I
       | need to declare "Self"? If so, will be "Self" part of the
       | struct's memory layout?
        
         | bobbylarrybobby wrote:
         | I think Rust does this quite well. There are a few handy rules.
         | 
         | 1. If the first argument of a function is `self`, then that
         | function is treated as a method and can be called like
         | `obj.meth(args)`. Functions without `self` must be called
         | statically, which in the case of an object would look like
         | `Type::meth(obj, args)`. The second form is used occasionally
         | by common wrapper types like Box and Rc to avoid dereferencing
         | "piercing the veil" and calling a method on the wrapped object
         | (Rust has no separate `obj->meth` syntax, which IMO is its own
         | issue). 2. Having access to `Self` lets you specify the types
         | of _non_ -this arguments as well as return types, like `fn
         | add(self, other: Self) -> Self {...}`. You can also use it as
         | the name of the type when (de)structing objects, e.g., `let
         | Self { fields } = other` or `return Self { fields }`. Writing
         | out the name of the current type each time could get unwieldy.
         | 3. `Self` as a namespace is handy when dealing with enums,
         | e.g., `match self { Self::First => ..., Self::Second => ...}`
         | or static functions like `Self::func(args)`. It's crucial in
         | trait implementations when referring to associated types, e.g.,
         | in Iterator there's `type Item; fn next(&mut self) ->
         | Option<Self::Item>;`
         | 
         | I don't why a Self variable would be placed on the stack
         | differently from non-Self variables; method calls have always
         | just been syntactic sugar + namespacing for a free function,
         | no?
        
         | flohofwoe wrote:
         | There's nothing special about the 'Self' (it's just a type).
         | 
         | In general, consts in Zig are purely compile time things and
         | don't take up memory.
         | 
         | And putting a function inside a struct which takes a pointer to
         | its struct type as first argument just allows method-call-
         | syntax-sugar, but you can also write it as regular function
         | (which must then be namespaced with the struct type though):
         | const Bla = struct {             const Self = @This();
         | val: i32 = 0,             fn add(self: *Self, val: i32) void {
         | self.val += val;             }         };              pub fn
         | main() void {             var bla = Bla{};             // with
         | method-call syntax sugar             bla.add(2);             //
         | without method-call syntax sugar             Bla.add(&bla, 3);
         | }
         | 
         | ...but in this case it's probably better to not use Self and
         | @This() (it mostly makes sense with generics).
         | const Bla = struct {             val: i32 = 0,             fn
         | add(self: *Bla, val: i32) void {                 self.val +=
         | val;             }         };
        
         | quietbritishjim wrote:
         | > Will I need to declare "Self"? If so, will be "Self" part of
         | the struct's memory layout?
         | 
         | I'm only familiar with C++, not Zig, but it doesn't seem that
         | different to me. If a C++ class includes a typedef, then that
         | doesn't contribute to the class's footprint.
         | 
         | Even the explicit self parameter is not so different. In C++, a
         | method can be marked as const or even && (rvalue reference) and
         | that's applied to the implicit this parameter. If anything, it
         | would be clearer if this was an explicit parameter; as of
         | C++23, it actually is allowed to be [1], called "deducing
         | this".
         | 
         | [1] https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
        
         | wchar_t wrote:
         | Nope, since Self is a declaration and not a field it won't take
         | up memory.
        
       | avinassh wrote:
       | why this wouldn't be possible (or not preferred) without the dot?
       | var tea = Tea{.full = true};
       | 
       | like:                   var tea = Tea{full = true};
        
         | pornel wrote:
         | Languages tend to avoid having parsing ambiguities and syntaxes
         | that require unbounded lookahead, because things like that make
         | parsers slower and/or more complex. This is a pain not just for
         | the compiler, but also syntax highlighting, IDEs and other
         | tooling. Languages also need to consider future syntax
         | extensions and ability to give good error messages for syntax
         | errors. This usually requires some redundancy and extra sigils
         | or keywords.
         | 
         | Here `full = true` is a valid expression syntax, and it'd be
         | problematic if `{` could start a block of code here. This case
         | may be parseable unambiguously thanks to `Tea` ident in the
         | front, but e.g. if Zig ever wanted to add something like
         | Swift's final closure syntax, then `expr { expr }` could become
         | valid, and this would be ambiguous.
         | 
         | In Rust `if struct {}` is a parsing edge case. JS has ambiguous
         | `{field: value}` objects and `{label: code}` blocks. CSS
         | struggles to add nested rules, because syntaxes of `selector {
         | property:value` and `selector { selector:selector` overlap, and
         | that requires slower and/or more complicated parsers.
        
         | throwawaymaths wrote:
         | I'll counter with one reason why to prefer the dot: it mirrors
         | the C99 standard. Part of zig's ethos is being friendly to C
         | programmers.
         | 
         | https://stackoverflow.com/questions/32698293/assign-values-t...
        
           | brabel wrote:
           | Yep, I also noticed that Zig just does what C does... coming
           | from other C-like languages, like Java, JS, Go, though, which
           | don't do that, I was also surprised.
        
       | frodowtf wrote:
       | Haven't used Zig but that comptime coercion seems very annoying.
        
       | pjmlp wrote:
       | Well the amount of @ uses, it seems either to be to appeal to
       | annotation heavy Java folks, or recovering Objective-C users.
        
         | kristoff_it wrote:
         | @ is just the prefix used by builtins, it's not used for
         | anything else.
        
           | pjmlp wrote:
           | Hence recovering Objective-C users.
        
             | kps wrote:
             | Would you prefer $ for recovering VMS users?
        
               | pjmlp wrote:
               | I would prefer no prefixes at all.
               | 
               | We already have Perl, Ruby, UNIX shell languages, PHP and
               | classical BASIC for that, among others that have faded
               | away, no need for more.
        
         | kuon wrote:
         | Usually, I never use @ in code directory, I use either stdlib
         | or I write my own wrapper with added semantic.
        
         | pavlov wrote:
         | Or maybe Rogue/Nethack players who identify with @.
        
       | nixpulvis wrote:
       | Correct me if I'm wrong, but it really seems like bad const
       | inference that `x` from the 8th example is typed as a
       | `comptime_int`. Is all comptime inference only done at the point
       | of declaration?
        
       | lionkor wrote:
       | The biggest quirk is arrays vs slices, their notation, and
       | pointer types. Its a nightmare
        
         | Joker_vD wrote:
         | Arrays vs slices seem to be pretty much the same as in Go, and
         | the notation is completely straightforward: "ARRAY 100 OF
         | INTEGER", except instead of "ARRAY" and "OF", the "[" and "]"
         | are used -- type constructor goes first, before its arguments,
         | as it should be.
         | 
         | As for pointer types, they look reasonable but can't say much
         | more.
        
           | kristoff_it wrote:
           | One correction: Go slices also own their memory, so their
           | equivalent would be Zig's `std.ArrayList`. Slices in Zig are
           | just ptr+len, so you will have to manage the underlying
           | memory separately. This makes sense for Zig since it's a
           | lower-level language than Go.
        
             | masklinn wrote:
             | > One correction: Go slices also own their memory
             | 
             | They both do and don't, which is a major issue with the
             | language.
             | 
             | If you create a slice from an array, it'll use that array
             | as backing buffer. Likewise if you reslice and existing
             | slice.
             | 
             | But because it _also_ has an arraylist interface (kinda,
             | it's a bad one) you can quite easily stomp on other slices
             | using the same backing buffer.
        
               | Joker_vD wrote:
               | Oh yes, I've had some quite hilarious (read: infuriating
               | to debug) bugs because of that behaviour.
               | 
               | I imagine that's why almost everyone just use slices
               | exclusively: you rarely if ever see
               | "[SomeConstant]whatever { ... }" or even "[...]whatever {
               | ... }" in Go codebases, it's almost always just
               | "[]whatever { ... }": such literal slices have copy-on-
               | append behaviour. And the syntax really nudges you into
               | it which is nice.
        
               | masklinn wrote:
               | > such literal slices have copy-on-append behaviour.
               | 
               | Only because literal slices default to cap == len (and a
               | buffer of the same size). But if you create a sub-slice
               | then all bets are off. That's why some folks recommend
               | always using a "full slice" if the result escapes:
               | A[x:y:y]
               | 
               | This way the cap() is set to the same value as the len(),
               | and append will always trigger a copy. Essentially a
               | cheaper (but riskier) version of a defensive copy.
        
         | throwawaymaths wrote:
         | it's difficult, but they are there for a reason. Working
         | through them helped me appreciate all of the different options
         | that exist (and yes sentinel termination is sometimes better:
         | https://lemire.me/blog/2020/09/03/sentinels-can-be-faster/)
         | 
         | If there's one thing that's a nightmare about how zig handles
         | these is that sometimes coercion between array types is a bit
         | hidden and the rules are not obvious, though their application
         | in code is usually straightforward and you don't have to think
         | about it too much (AFAICT it doesn't let you do the wrong
         | thing)
        
         | kristoff_it wrote:
         | The notation is very "compressed", that's for sure.
         | 
         | On the other hand, manipulating sequences of data and pointers
         | of various kinds is one of the main things you do when
         | programming, and concise notation (especially in a low level
         | programming language) is akin to how mathematicians have
         | concise notation for the most common objects they manipulate in
         | their formulas.
        
       | xchkr1337 wrote:
       | Are tabs supported yet?
        
         | nektro wrote:
         | I hope not
        
         | kristoff_it wrote:
         | Yes, have been for a while now.
        
           | latch wrote:
           | Is there an update since this 2 year old thread (1) which
           | leaves the door open to removing tab support in the future?
           | 
           | I use relatively large fonts for medical reasons and tabs are
           | much more accessible to me.
           | 
           | (1) https://github.com/ziglang/zig/issues/544
        
       | stephc_int13 wrote:
       | Some parts of the Zig syntax reminds me of the old K&R style
       | function declaration, that seemed absurd and was replaced at some
       | point, but it was released, used in production and defended for a
       | while.
       | 
       | There is often a strong rationale behind clumsy syntax decisions,
       | I hope they will fix those quirks.
        
       | rstarast wrote:
       | Re 9, would it be possible to change the signature of expectEqual
       | from                   pub fn expectEqual(expected: anytype,
       | actual: @TypeOf(expected)) !void
       | 
       | to                   pub fn expectEqual(expected:
       | @TypeOf(actual), actual: anytype) !void
       | 
       | What would the downsides be?
        
         | nyanpasu64 wrote:
         | Why not place actual as the first argument and expected as the
         | second? This matches the way I hand-write asserts like
         | `assert(x == 5)`.
        
         | ptato wrote:
         | It isn't possible, compiler will complain about "actual" being
         | an undeclared identifier. The function arguments are declared
         | in order.
        
           | hiccuphippo wrote:
           | The compiler already allows using functions defined later in
           | the same file. This is a solvable problem.
        
         | latch wrote:
         | No, it won't work. It'll say "actual" is undeclared.
         | 
         | https://github.com/ziglang/zig/issues/4437 is tracking the
         | issue. I think std.testing is going to see some major changes,
         | so it's possible this gets fixed. But going from the responses
         | in that issue, I think that fix might be more of a side effect,
         | since there doesn't seem to be too much sympathy for the issue
         | as-is.
        
       | adamrezich wrote:
       | > Speaking of structure fields, they're always public. Structures
       | and functions are private by default with an option to make them
       | public. But struct fields can only be public. The recommendation
       | is to document allowed/proper usage of each field.
       | 
       | > I don't want to editorialize this post too much, but it's
       | already caused the type of issues that you'd expect, and I think
       | it'll only cause more difficulties in a 1.x world.
       | 
       | I wish the author elaborated a bit more here because I've always
       | been interested in what problem exactly `private` (and all the
       | language complexity/overhead) that comes with it solves. for
       | something like C# it kind of makes sense because it's a language
       | built around chaining method calls and IntelliSense, so the user
       | types `foo.` and IntelliSense shows literally everything you
       | could want to do with that `Foo` instance, excluding `private`
       | members/methods. I can see the use-case for this in a corporate-
       | type setting. in absence of this feature (which I _believe_ is
       | the case for Zig?), what possible difficulties can be caused, in
       | practice?
        
       ___________________________________________________________________
       (page generated 2023-03-27 23:02 UTC)