Live data from Hacker News

Zig; what I think after months of using it

strongly-typed-thoughts.net

41–50 of 181 posts

Re: Zig; what I think after months of using it

#41
post #20

The debate between static and dynamic typing continues unceasingly. Even when the runtime values are statically typed, it's merely reprised at the type level.

The debate seems to have mostly ended in a victory for static types. The largest languages other than Python have them (if you include the transition from JS to TS). Python is slowly moving toward having them too.

The whole anytype/trait question is just dynamic typing, but at the type level instead of the value level.

Re: Zig; what I think after months of using it

#42
post #12

No idea how much the author is experienced at Zig, but my thoughts: > No typeclasses / traits This is purposeful. Zig is not trying to be some OOP/Haskell replacement. C doesn't have traits/typeclasses either. Zig prefers explicitness over implicit hacks, and typeclasses/traits are, internally, virtual classes with a vtable pointer. Zig just exposes this to you. > No encapsulation This appears to be more a documentat…

Having just gone down this road in C#, the way Unicode is now handled is via "runes". Each rune may be comprised of various Unicode characters, which may themselves be 1-4 bytes (in the case of utf-8 encoding). The one problem I have with this approach is that all of the categorization features operate a level below the runes, so you still have to break them up. The biggest drawback is that, at least in my (admittedl…

Go does this too. I generally like the idea a lot, as long as it's consistent. The one thing I don't like is the inconsistency.

Not sure about C#, but in Go for example ranging strings ranges over runes, but indexing pulls a single byte. And len is the byte length rather than rune length.

So basically it's a byte array everywhere except ranging. I guess I would have preferred an explicit cast or conversion to do that instead of by default.

Re: Zig; what I think after months of using it

#43
post #7

When did shadowing become a feature? I was under the impression it's an anti-pattern. As per the example in the article > const foo = Foo.init(); > const foo2 = try foo.addFeatureA(); > const foo3 = try foo.addFeatureB(); It's a non issue to name vars in a descriptive way referring to the features initial_foo for example and then foo_feature_a. Or name them based on what they don't have and then name it foo. In the e…

> Replacing the value of one variable constantly throughout the code could lead to unpredictable bugs. Having variables with scopes that last longer than they're actually used and with names that are overly long and verbose leads to unpredictable bugs, too, when people misuse the variables in the wrong context later. When I have `initial_foo`, `foo_feature_a`, and `foo_feature_b`, I have to read the entire code caref…

> When I have `initial_foo`, `foo_feature_a`, and `foo_feature_b`, I have to read the entire code carefully to be sure that I'm using the right `foo` variant in subsequent code. If I later need to drop Feature B, I have to modify subsequent usages to point back to `foo_feature_a`. Worse, if I need to add another step to the process—a Feature C—I have to find every subsequent use and replace it with a new `foo_feature_c`. And every time I'm modifying the code later, I have to constantly sanity check that I'm not letting autocomplete give me the wrong foo!

When you have only one `foo` that is mutated throughout the code you are forced to organize the processes in your code (validation, business logic) based on the current state of that variable. If your variables have values which are logically assigned you're not bound by the current state of that variable. I think this a big pro. The only downside most people disagreeing with me are mentioning is related to ergonomics of it being more convenient.

Re: Zig; what I think after months of using it

#44
post #7

When did shadowing become a feature? I was under the impression it's an anti-pattern. As per the example in the article > const foo = Foo.init(); > const foo2 = try foo.addFeatureA(); > const foo3 = try foo.addFeatureB(); It's a non issue to name vars in a descriptive way referring to the features initial_foo for example and then foo_feature_a. Or name them based on what they don't have and then name it foo. In the e…

Shadowing is a feature. It's very common that given value transforms its shape and previous versions become irrelevant. Keeping old versions under different names would be just confusing. With type system there is no room for accidental misuse. I write Rust professionally for > 2 years, and years before that I was using it my own projects. I don't think shadowing ever backfired on me, while being very ergonomic.

Depending on which language you are using shadowing could lead to either small issues or catastrophic ones (in the scope of the program). If you have Python and you start with a number but end up with a complex dict this is very different than having one value in Rust and a slightly different value which is enforced by the compiler.

Re: Zig; what I think after months of using it

#45

Earlier quoted context omitted.

Over the years, I’ve wasted 1-2 days of my life debugging bugs caused by unintentional variable shadowing in Go (yes, I’ve kept track). Often, the bug is caused by an accidental use of := instead of =. I don’t understand why code that relies on shadowing isn’t harder to follow. Wish I could disable it entirely.

> Often, the bug is caused by an accidental use of := instead of =. This is a distinctly Go problem, not a problem with shadowing as a concept. In Rust you'd have to accidentally add a whole `let` keyword, which is a lot harder to do or to miss when you're scanning through a block. There are lots of good explanations in this subthread for why shadowing as a concept is great. It sounds like Go's syntax choices make it…

> There are lots of good explanations in this subthread for why shadowing as a concept is great

Not really. All of them boil down to ergonomics, when in reality it doesn't bring a lot of benefit other than people hating on more descriptive variable names (which is fair).

Re: Zig; what I think after months of using it

#47
post #12

No idea how much the author is experienced at Zig, but my thoughts: > No typeclasses / traits This is purposeful. Zig is not trying to be some OOP/Haskell replacement. C doesn't have traits/typeclasses either. Zig prefers explicitness over implicit hacks, and typeclasses/traits are, internally, virtual classes with a vtable pointer. Zig just exposes this to you. > No encapsulation This appears to be more a documentat…

> Zig does have destructors, in a way. It's called defer and errordefer.

defer ties some code to a static scope. Destructors are tied to object lifetime, which can be dynamic. For example, if you want to remove some elements from an ArrayList of, say, strings, the string's would need to be freed first. defer does not help you, but destructors would.

Re: Zig; what I think after months of using it

#48

Earlier quoted context omitted.

Is there a myth that makes that claim? Virtually every take I've heard is that Zig is "safe enough" while giving developers more control over memory and actually, it's specifically better for cases where you must write unsafe code, as it's not possible to express all programs in safe Rust.

If you must write unsafe code, what's wrong with just dropping down to unsafe in Rust when you need to? You have all the power unsafe provides, and you have a smaller surface area to audit than if your entire codebase resides in one big unsafe block.

Unsafe Rust is problematic: https://zackoverflow.dev/writing/unsafe-rust-vs-zig See also: https://github.com/roc-lang/roc/blob/main/www/content/faq.md...

Zig is not entirely unsafe. It provides quite a few compile time checks and primitives to catch memory leaks or prevent them altogether.

Re: Zig; what I think after months of using it

#49
post #12

No idea how much the author is experienced at Zig, but my thoughts: > No typeclasses / traits This is purposeful. Zig is not trying to be some OOP/Haskell replacement. C doesn't have traits/typeclasses either. Zig prefers explicitness over implicit hacks, and typeclasses/traits are, internally, virtual classes with a vtable pointer. Zig just exposes this to you. > No encapsulation This appears to be more a documentat…

For me not having strings in Zig and being forced to use the fairly verbose '[]const u8' syntax every time I need a string was a little annoying at first, but it has had the effect of making me comfortable with the idea of buffers in a general sense, which is critical in systems programming. Most of the things that irked me about Zig when first learning it (I'm only a few weeks into it) have grown on me.

[deleted]

Re: Zig; what I think after months of using it

#50
post #28

Earlier quoted context omitted.

They're really just enum variants. You can easily capture the error and conditionally handle it: fn failFn() error{Oops}!i32 { try failingFunction(); return 12; } test "try" { const v = failFn() catch |err| { try expect(err == error.Oops); return; }; try expect(v == 12); // is never reached }

> You can easily capture the error and conditionally handle it Sure. But the compiler won't help you check that your function only throws the errors that you think it does, or that your try block is handling all the errors that can be thrown inside it.

> ...the compiler won't help you check that your function only throws the errors that you think it does, or that your try block is handling all the errors that can be thrown inside it.

It will do both of those:

    const std = @import("std");

    fn throws(i: usize) !void {
        return switch (i) {
            0 => error.zero,
            1 => error.one,
            else => error.many,
        };
    }

    fn catches(i: usize) !void {
        throws(i) catch |err| {
            return switch (err) {
                error.one => error.uno,
                else => |other| other,
            };
        };
    }

    pub fn main() void {
        catches(std.os.argv.len) catch |err| {
            switch (err) {
                // Type error if you comment out any of these:
                // note: unhandled error value: 'error.zero'
                error.zero => std.debug.print("0\n", .{}),
                error.uno => std.debug.print("1\n", .{}),
                error.many => std.debug.print("2\n", .{}),
                // Type error if you uncomment this:
                // 'error.one' not a member of destination error set
                //error.one => std.debug.print("1\n", .{}),
            }
        };
    }
It wouldn't hurt to just read the docs before making confident claims.
Post reply on HN