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.
Zig; what I think after months of using it
41–50 of 181 posts
Re: Zig; what I think after months of using it
#42No 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…
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
#43When 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 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
#44When 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.
Re: Zig; what I think after months of using it
#45Earlier 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…
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
#46Re: Zig; what I think after months of using it
#47No 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…
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
#48Earlier 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.
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
#49No 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.
Re: Zig; what I think after months of using it
#50Earlier 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.
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.