35 ms·
Zig Quirks
- pjmlp 4y agoWell the amount of @ uses, it seems either to be to appeal to annotation heavy Java folks, or recovering Objective-C users.
- pavlov 4y agoOr maybe Rogue/Nethack players who identify with @.
- kristoff_it 4y ago@ is just the prefix used by builtins, it's not used for anything else.
- kuon 4y agoUsually, I never use @ in code directory, I use either stdlib or I write my own wrapper with added semantic.
- kuon 4y agoDirectly*
- lionkor 4y agoThe biggest quirk is arrays vs slices, their notation, and pointer types. Its a nightmare
- Joker_vD 4y agoArrays 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 4y agoOne 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 4y ago> 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 4y agoOh 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.
- rstarast 4y agoRe 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?
- ptato 4y agoIt isn't possible, compiler will complain about "actual" being an undeclared identifier. The function arguments are declared in order.
- hiccuphippo 4y agoThe compiler already allows using functions defined later in the same file. This is a solvable problem.
- latch 4y agoNo, it won't work. It'll say "actual" is undeclared. https://github.com/ziglang/zig/issues/4437 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.
- nyanpasu64 4y agoWhy 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 4y agoPlease 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 4y agoSelf 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 4y ago1.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 4y agoI’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.
- the_mitsuhiko 4y ago> 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.
- fatneckbeard 4y agotwo hardest problems in programming what to name things cache coherency off by one bugs
- sophiabits 4y agoThe 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 4y agoI 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.
- pcwalton 4y agoI fought to keep this feature around in Rust. I was inspired by OCaml (which the old Rust compiler was written in), where you could write: let x = foo() in let x = bar x in let x = baz x in print x In a functional language where mutation is less convenient than in C++, this is really handy, and I wanted Rust to support the same idiom.
- sophiabits 4y agoExactly! 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.
- xchkr1337 4y agoAre tabs supported yet?
- kristoff_it 4y agoYes, have been for a while now.
- latch 4y agoIs 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 https://github.com/ziglang/zig/issues/544
- nektro 4y agoI hope not
- quietbritishjim 4y ago(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 4y ago> 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 4y agoAh 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 4y agoYep! 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.
- ngrilly 4y agoRegarding 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 https://github.com/ziglang/zig/issues/1097
- kuon 4y agoI have PTSD about 1097 :D
- overthrow 4y agoThen 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-1404627009 https://github.com/ziglang/zig/issues/1097#issuecomment-1404...
- JonChesterfield 4y agoGood discussion there, thanks for linking. Shout out to llvm for sticking with camelCase while the c++ library is all snake_case. Turns out eventually one gets used to reading mixtures of them and only occasionally writes dense_map or unorderedMap, but I still wish it wasn't like this. edit: I write large patches in the one true style and then begrudgingly fix them up for review using ad hoc emacs macros but it looks like clang-tidy can automate that. Wonder if it's robust enough to bidirectionally convert between review acceptable and legible
- 0x37 4y agoI 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 4y ago> 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 4y agoAlthough 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 4y agoThis 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_ 4y agoMy 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/ https://ziglang.org/documentation/master/ [1]: https://ziglang.org/documentation/master/std/ https://ziglang.org/documentation/master/std/
- ptato 4y agoBuilt-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.
- nromiun 4y ago> 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?
- TeaDude 4y agoDid they remove that? I thought that tabs were still forbidden...
- nromiun 4y agohttps://github.com/ziglang/zig/issues/544 https://github.com/ziglang/zig/issues/544 Maybe? They say their stage2 parser accepts them now.
- generichuman 4y agoI 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.
- nromiun 4y agoIsn'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-614260816 https://github.com/ziglang/zig/issues/1097#issuecomment-6142...
- canadianfella 4y ago[dead]
- zamalek 4y agoLike int_age and string_name make the type of a value obvious? Hard pass.
- 4y ago
- stephc_int13 4y agoSome 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.
- avinassh 4y agowhy this wouldn't be possible (or not preferred) without the dot? var tea = Tea{.full = true}; like: var tea = Tea{full = true};
- throwawaymaths 4y agoI'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-to-structure-variables https://stackoverflow.com/questions/32698293/assign-values-t...
- brabel 4y agoYep, 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.
- pornel 4y agoLanguages 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.
- Joker_vD 4y ago> Here `full = true` is a valid expression syntax, and it'd be problematic if `{` could start a block of code here. Golang has almost this exact problem in its grammar but in practice they manage just fine even though there are indeed some edge cases where parser gets confused: > A parsing ambiguity arises when a composite literal using the TypeName form of the LiteralType appears as an operand between the keyword and the opening brace of the block of an "if", "for", or "switch" statement, and the composite literal is not enclosed in parentheses, square brackets, or curly braces. In this rare case, the opening brace of the literal is erroneously parsed as the one introducing the block of statements. To resolve the ambiguity, the composite literal must appear within parentheses. if x == (T{a,b,c}[i]) { … } if (x == T{a,b,c}[i]) { … }
- scoutt 4y agoComing 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?
- wchar_t 4y agoNope, since Self is a declaration and not a field it won't take up memory.
- quietbritishjim 4y ago> 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/ https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
- flohofwoe 4y agoThere'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; } };
- deleted 4y ago[deleted]
- haberman 4y ago> 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-942686366 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.
- adamrezich 4y ago> 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?
- nixpulvis 4y agoCorrect 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?
- frodowtf 4y agoHaven't used Zig but that comptime coercion seems very annoying.
- rurban 4y agoIn my compiler I loosely coerce comptime_int's to int's if needed by subsequent API's. You also need to expand or truncate it's size on demand. E.g. C compilers do that. zig just error's, which is a silly behaviour.