7 ms·
I'm a C coder, a big fan of Rust and while I've heard of Zig before I still don't really get what it's going for. As far as I can tell it's Rust without the bor
by simias 8y ago
I'm a C coder, a big fan of Rust and while I've heard of Zig before I still don't really get what it's going for. As far as I can tell it's Rust without the borrow checker and the mental overhead that goes with it. Maybe it's because I'm wearing my Rust glasses but some design choices seem a bit strange to me, for instance regarding Zig's unions (equivalent to Rust's enums or C's tagged unions):
https://ziglang.org/documentation/master/#union https://ziglang.org/documentation/master/#union
const Payload = union {
Int: i64,
Float: f64,
Bool: bool,
};
[...]
var payload = Payload {.Int = 1234};
// payload.Float = 12.34; // ERROR! field not active
assert(payload.Int == 1234);
// You can activate another field by assigning the entire union.
payload = Payload {.Float = 12.34};
assert(payload.Float == 12.34);
[...]
// Unions can be given an enum tag type:
const ComplexTypeTag = enum { Ok, NotOk };
const ComplexType = union(ComplexTypeTag) {
Ok: u8,
NotOk: void,
};
[...]
const c = ComplexType { .Ok = 0 };
assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
That's weird ergonomics IMO and quite a lot of typing. As far as
I can tell there's no type inference? Also I'm not sure why const is used everywhere, even for things like type declarations, does Zig allow messing with types at runtime? That seems like a pretty high level language feature so it's quite interesting if it does.
Anyway, from what I see it's basically somewhat halfway
between C and Rust, with some of the syntactic sugar of Rust but
not the borrow checker and everything that goes along with it.
That being said the language is still very young and at this
stage Rust was a bit of a mess as well, what with the numerous
pointer types, green threads and the like... Maybe as they progress toward 1.0 they'll be able to expose a clearer picture of what they're doing.
- AndyKelley 8y agoI'd be interested in seeing a code comparison of equivalent Rust and Zig code with regards to tagged unions, to demonstrate your claim. I could help you with the Zig code if you provide the Rust code. Also to answer your questions: > As far as I can tell there's no type inference? You can see an example of type inference here: https://ziglang.org/documentation/master/#Hello-World https://ziglang.org/documentation/master/#Hello-World > does Zig allow messing with types at runtime? Yes. https://ziglang.org/documentation/master/#Generic-Data-Structures https://ziglang.org/documentation/master/#Generic-Data-Struc... Edit: my mistake, I misread or didn't see the "at runtime" part. It only allows messing with types at compile-time.
- simias 8y ago>I'd be interested in seeing a code comparison of equivalent Rust and Zig code with regards to tagged unions, to demonstrate your claim. I could help you with the Zig code if you provide the Rust code. Well in Rust you don't have to define the enum and the union separately, actually in Rust unions and enums are effectively the same thing (outside of C FFI that is). So you in Rust you have something like: enum ComplexType { Ok(u8), NotOk, } Then to unpack it you always have to destructure it, you can't just do my_complex_type.Ok() or something like that. You can create accessor methods if that makes sense for your enum though, for instance for this ComplexType you could implement an "unwrap" function that returns the u8 in case of success and panics in case of an error. In general you tend to avoid those unless they really make sense, as a rule of thumb it's better to "match" the type (which seems similar to Zig's switch statement with enums). Beyond that I dug a bit deeper in the docs and I don't quite understand how generics are supposed to work exactly, at first they look like what I'm used to in other languages but then I get to the generic printf example and I see this code: pub fn printValue(self: *OutStream, value: var) !void { const T = @typeOf(value); if (@isInteger(T)) { return self.printInt(T, value); } else if (@isFloat(T)) { return self.printFloat(T, value); } else { @compileError("Unable to print type '" ++ @typeName(T) ++ "'"); } } Is it just for the sake of the example or does it just exhibit a limitation of the generic system? What would happen if I wanted to be able to print a custom type for instance? Also, if generics exist, why are things like "@clz" or "@intToFloat" compiler builtins, surely they could be implemented as part of the stdlib using a generic method? Although I see that "@memset" and "@memcpy" are also builtin and I can't quite figure out why so maybe I'm missing something here.
- AndyKelley 8y agoIn Zig: const ComplexType = union(enum) { Ok: u8, NotOk, }; It's recommended to use destructuring syntax to unpack it, but you have the option to use fields directly which invokes runtime safety checks. This matches the syntax for normal unions, which do not have tags, but still have runtime safety checks. Rust does not have this feature - unions must always have tags - where as in Zig the state that determines which tag is active in a union could be somewhere else. > What would happen if I wanted to be able to print a custom type for instance? const std = @import("std"); pub fn main() void { const ComplexType = union(enum) { Ok: u8, NotOk, }; var ct = ComplexType { .Ok = 123 }; std.debug.warn("{}\n", ct); ct = ComplexType.NotOk; std.debug.warn("{}\n", ct); } Outputs: ComplexType{ .Ok = 123 } ComplexType{ .NotOk = void } This is all implemented in userland in the standard library. Last time I checked, in Rust the equivalent code is implemented with a macro hard coded in the compiler. > why are things like "@clz" or "@intToFloat" compiler builtins, surely they could be implemented as part of the stdlib using a generic method? They could be implemented in userland, but then they would possibly not lower to the LLVM primitives that they represent. In the case of the casting functions, it's more to do with semantics of the language, making it hard to accidentally shoot yourself in the foot. Shooting yourself in the foot on purpose, however, is always allowed.
- deleted 8y ago[deleted]
- gradschool 8y agoThere's no need for uncertainty about what Zig is going for. Rust code blows up when the heap runs out regardless of how carefully you write it unless you want to forego the standard library and whatever depends on it. Zig gives you control over memory allocation and hence at least the potential to write correct code.
- scottlamb 8y ago> There's no need for uncertainty about what Zig is going for. Rust code blows up when the heap runs out regardless of how carefully you write it unless you want to forego the standard library and whatever depends on it. Zig gives you control over memory allocation and hence at least the potential to write correct code. I hope that's not it. Talk about throwing out the baby with the bathwater. It's true that Rust's standard library panics on ENOMEM, but not only is this not inherent to the language, it's possible to extend the standard library, and folks are doing so. See https://github.com/rust-lang/rust/issues/48043 https://github.com/rust-lang/rust/issues/48043 for support of "fallible allocations".
- gradschool 8y agoI agree that Rust has a lot of good ideas in it. The notion of ownership isn't far off my own way thinking about pointers when writing code with pointers in it, and I welcome any help from the compiler at avoiding my stupid mistakes, but if worse comes to worst, at least my mistakes are fixable. In the old days it would have been considered bad practice to postpone considerations of storage limitations to some future release, but maybe I'm just showing my age.
- steveklabnik 8y agoThe decision was made with pragmatism in mind. What do most programs in Rust want to do? Most of them are fine with panic on an error. Making your code robust against this, at least in Rust, makes things a lot more cumbersome, and most programs would panic upon such an error anyway. Most programs that care about this possibility aren't using the standard library anyway. Therefore, for the standard library, the decision was made to panic on OOM, and to not block the release of Rust 1.0 on something that, at the time, wasn't itself a stable feature. Three years later, I'm comfortable with this decision; three more years of waiting on Rust 1.0 would have killed it as a language. Many, many useful programs have been written in Rust since then. Of course, you can disagree with this decision; I mostly want to communicate that it was not a flippant "who needs to care about that," but a careful decision made after weighing the tradeoffs. In the old days, those tradeoffs were very different.