Live data from Hacker News

Zig is hard but worth it

ratfactor.com

271–280 of 307 posts

Re: Zig is hard but worth it

#271
post #195
post #37

Alternative languages are cool, but I struggle to see the point of a systems programming language that doesn't offer static memory safety in 2023. Rust isn't necessarily the best and final answer--it seems like there is a broad design space to explore for memory-safe systems programming languages. But Zig seems to occupy the same local maximum as C--a relatively simple, non-safe systems language--and doesn't have a k…

Honest question here (that I have every time I see someone talk about memory safety in 2023), are you aware of Ada/Spark?

If Ada and Spark solve problems as well or better than Rust and have been around for longer, why have I never seen any program written in them?

I don’t know anything about ada/spark but it seems clear that something has gone terribly wrong for them to not have taken off like Rust has.

Re: Zig is hard but worth it

#272
post #37

Alternative languages are cool, but I struggle to see the point of a systems programming language that doesn't offer static memory safety in 2023. Rust isn't necessarily the best and final answer--it seems like there is a broad design space to explore for memory-safe systems programming languages. But Zig seems to occupy the same local maximum as C--a relatively simple, non-safe systems language--and doesn't have a k…

If we’re comparing C to Zig I’m not sure what memory safety even needs to be mentioned for. For C to Zig there’s plenty of reasons one might prefer Zig. For memory safety obviously you might opt to choose neither.

If you are going to leave C, why take a half step when you could go the full way and end up with the better tool.

Re: Zig is hard but worth it

#273
post #71

Earlier quoted context omitted.

It's significantly nicer to write than C (my opinion obviously). I see it as a general purpose language. But mostly the team decided to do this because we wanted to unify on one language and double down on the investment in Zig. I'm not a fanboy (nothing wrong if anyone is, just clarifying about myself); I think this choice was right.

For what it's worth I think this is an excellent choice. Back in 2019 I was deciding whether I wanted to pursue Zig full-time and one of the upsides that I determined was that once you reach critical mass writing all of your code for tools and things in Zig you end up with things that are really exactly what you need and with a very high baseline for speed, flexibility, and so on. Right now I'm considering the same t…

how your experience with Odin so far? How Odin differ from Zig in your opinion? What you likes/dislikes compared to Zig or other languages?

Re: Zig is hard but worth it

#274
post #38
post #7

I get what Zig is going for in making all operations as explicit as possible, but I fear that it's going to turn away fields like graphics and game development where it would be a good fit except for the lack of operator overloading forcing you to go back to C-style math function spaghetti. It's all fun and games until what should be a straightforward math expression turns into 8 nested function calls.

As someone who works on another language that is relatively reluctant to add language features (Java) we regularly face such dilemmas. A user shows up with a problem that could be helped by the language. The problem is real and a language feature would work, but there are many such problems, and adding features to solve all of them will make the language much bigger, overall causing greater harm (even those who don't…

>and adding features to solve all of them will make the language much bigger, overall causing greater harm (even those who don't themselves use the feature need to learn it to be able to read code).

I agree that adding too many features can make a language too large and bloated. However, I disagree that this is always the case. For example, adding features that make it easier to code math is not necessarily a bad thing. In fact, it is a good thing, as it can make programming more accessible to a wider range of people.

Additionally, math is often used in fields that require high speed, such as computer graphics and game development, Computer vision, Robotics, Machine learning, Natural language processing (NLP), Mathematical modeling, all kinds of scientific computing (Computational physics, Computational chemistry, Computational biology...) As a result, low-level programming languages are often used to implement the core code in these fields. As you see, Math is essential for many fields.

Re: Zig is hard but worth it

#275
post #26

Earlier quoted context omitted.

I'm no expert on zig, but the one area I have seen it shooting up in popularity is game dev. Though I guess that is largely as a replacement for C, so "C-style" wouldnt be much of a concern

Not really, it is mostly around communities like Handmade, most studios couldn't care less and it doesn't fit most engines that they are using.

[deleted]

Re: Zig is hard but worth it

#276
post #26

Earlier quoted context omitted.

I'm no expert on zig, but the one area I have seen it shooting up in popularity is game dev. Though I guess that is largely as a replacement for C, so "C-style" wouldnt be much of a concern

Not really, it is mostly around communities like Handmade, most studios couldn't care less and it doesn't fit most engines that they are using.

[deleted]

Re: Zig is hard but worth it

#277

Earlier quoted context omitted.

Using plus(a,b) for complex types sounds fine to me… I have to imagine if I were working with a huge file of these for a while it would start to feel normal, just like every language has a different syntax but you eventually feel comfortable using.

Are you sure you would find this more ergonomically pleasing: assign(x, plus(a, plus(b, plus(c, b)))) When you could have: x = a + b + c + d; Or: (let x (+ a b c d)) ?

is assign(x, plus(a, b, c, d)) not an option?

Re: Zig is hard but worth it

#278

Earlier quoted context omitted.

First of all that only works with vectors, but I want operator overloading to also work on things like matrices or custom types (for example quaterions, or a symmat3 struct that represents a symmetric 3x3 matrix using only 6 floats). Additionally, for efficient math code you often want vector / matrix types in AOSOA fasion: for example Vec3 to store an AVX lane for each X/Y/Z component. I want vector/matrix operation…

I have a marvelous proof that you can solve that with comptime but unfortunately the margins of this website are too small to contain it.

You can use pastebin, or even better, use github gist.

Re: Zig is hard but worth it

#279

Earlier quoted context omitted.

I disagree. I actively avoid languages that rely on package managers simply because they only give the illusion of being beneficial. It ends up being more boilerplate I have to learn to use an ecosystem, because they don't actually solve dependency hell and now there's a whole additional complicated tool with its own DSL I have to contend with in order to fix what's broken (or even diagnose issues.) The more peripher…

I'm not sure I understand this issue. The existence of a standard package manager doesn't prevent you from manually vendoring dependencies if you want to. I have personal experience working on node.js projects where several dependencies were just `cp` into a directory and treated like local modules, no npm involved at all. It's like `apt` or `brew` for system dependency management. It is there if you want it but you…

Furthermore, having a package manager indicates there are some kind of specifications behind so each package and repository has an expected format (not necessarily _a_ standard package manager; in many ecosystems multiple managers share one standard). This helps vendoring a lot since the standard specification makes the vendoring process very easy to automate and validate. Dependency managers in their essence is really just automated vendoring (or vendoring is just less automated dependency management); it helps because you can pick it apart and choose to automate only the parts you want, even if you don’t like the fully automated solution.

Re: Zig is hard but worth it

#280
post #273
post #71

Earlier quoted context omitted.

For what it's worth I think this is an excellent choice. Back in 2019 I was deciding whether I wanted to pursue Zig full-time and one of the upsides that I determined was that once you reach critical mass writing all of your code for tools and things in Zig you end up with things that are really exactly what you need and with a very high baseline for speed, flexibility, and so on. Right now I'm considering the same t…

how your experience with Odin so far? How Odin differ from Zig in your opinion? What you likes/dislikes compared to Zig or other languages?

I think Odin is terrifically designed overall. There are design choices that I was initially very skeptical about but when I decided to use the language they actually made a lot of sense.

Some overall differences between Odin and Zig and how I relate to them are:

## Exhaustive field setting

Zig requires you to set every field in a struct. Everything everywhere has to be initialized to something, even if it's `undefined` (which means it's the same thing as missing initialization in C, etc.).

Odin instead takes the position that everything is zero-initialized by default. It then also takes the position that everything has a zero value and that zero value is a valid value to have. The libraries think so, the users of the language think so and what you get when everything works like this is code that overwhelmingly talks exactly only about the things that need talking about, i.e. we only set the fields right now that matter.

Exhaustive field setting does remove uncertainty and I was very skeptical about just leaving things to zero-initialization, but overall I would say I prefer it. It really does work out most of the time and I've had plenty of bugs in Zig caused by setting something to `undefined` and just moving on, so it's not really as if that exhaustiveness check for fields was some 100% solution.

## An implicit context vs. explicit parameters

Zig is more or less known for using parameters to pass around allocators, and so on. It's not a new concept in most lower-level languages but it's one of the first languages to be known for baking this into the core community and libraries.

Odin does the same thing except it uses an implicit parameter in all Odin functions that is called `context`. When you want to change the allocator for a scope, you only need to set `context.allocator` (or `context.temp_allocator`) and every function you call in that scope will use that allocator. We can also write functions that take an optional parameter that defaults to the current allocator:

    join :: proc(
      a: []string,
      sep: string,
      allocator := context.allocator,
    ) -> (
      res: string,
      err: mem.Allocator_Error,
    ) {
    }
This way we get the same behavior and flexibility of talking about allocators but we can also default to either the basic default or whatever the user currently has in scope. We *can* also be more explicit if we want. The ability to have this implicit again makes it so we only need to talk about the things that are special in the code.

The context is also used for logger information, by the way, and you also have a `user_data` field that can be used to hold other stuff but I haven't really needed it for anything so far.

## Error information

Zig is known for its error unions, i.e. a set of error codes that can be inferred and returned from a function based on the functions it calls. These are nice and undoubtedly error handling in Zig is very neat because of it. However, there is a major downside: You can't actually attach a payload to these errors, so these are just tags that you propagate upwards. Not a huge deal but it is annoying; you'll have to have a parameter that you fill in when an error has occurred and you need more info:

    // `expect_error` here is only filled in with something if we have an error
    pub fn expect(self: *Self, expected_token: TokenTag, expect_error: *ExpectError) !Token {
    }
Odin instead has a system that works much the same, we can return early by using what is effectively the same as `try` in Zig: `or_return`. This will check the last value in the return type of the called function to see if it's an error value and return that error value if it is.

The error values that we talk about in Odin are just its normal values and can be tagged unions if we so choose. If we have the following Zig definitions for the `ExpectError` example:

    pub const ExpectTokenError = struct {
        expectation: TokenTag,
        got: Token,
        location: utilities.Location,
    };
    
    pub const ExpectOneOfError = struct {
        expectations: []const TokenTag,
        got: Token,
        location: utilities.Location,
    };
    
    pub const ExpectError = union(enum) {
        token: ExpectTokenError,
        one_of: ExpectOneOfError,
    };
We could represent them as follows and just use them as the error return value without holding a slot as a parameter that we will fill in:

    ExpectTokenError :: struct {
      expectation: TokenTag,
      got: Token,
      location: Location,
    }
    
    ExpectOneOfError :: struct {
      expectations: []TokenTag,
      got: Token,
      location: Location,
    }
    
    ExpectError :: union {
      ExpectTokenError,
      ExpectOneOfError,
    }
    
    expect_token :: proc(iterator: ^TokenIterator, expected_token: TokenTag) ->
        (token: Token, error: ExpectError) {
    }
They are normal tagged unions and in contrast to Haskell/Rust unions we don't have to bother with having different constructors for these just because a type is part of several different unions either, which is a big plus. We still get exhaustiveness checks, something akin to pattern matching with `switch tag in value { ... }`[0] and so on. Checking for a certain type in a union also is consistent across every union that contains that type, which is actually surprisingly impactful in terms of design, IMO.

## Vectors are special

This isn't going to have a Zig equivalent because, well, Zig just doesn't.

Odin has certain names for the first, second, third, etc., positions in arrays. This is because it's specifically tailored to programmers that might deal with vectors. It also has vector and matrix arithmetic built in (yes, there is a matrix type).

    array_long := [?]f32{1, 2, 3, 4, 5}
    array_short := [?]f32{0, 1, 2}

    fmt.printf("xyzw: %v\n", array_long.xyzw)
    fmt.printf("rgba: %v\n", array_long.rgba)
    fmt.printf("xyz * 2: %v\n", array_long.xyz * 2)
    fmt.printf("zyx + 1: %v\n", array_long.zyx + 1)
    fmt.printf("zyx + short_array: %v\n", array_long.zyx + array_short)
This gives the following output:

    > odin run swizzling
    xyzw: [1.000, 2.000, 3.000, 4.000]
    rgba: [1.000, 2.000, 3.000, 4.000]
    xyz * 2: [2.000, 4.000, 6.000]
    zyx + 1: [4.000, 3.000, 2.000]
    zyx + short_array: [3.000, 3.000, 3.000]
It seems like a small thing but I would say that this has actually made a fair amount of my code easier to understand and write because I get to at least signal what things are.

## Bad experiences

### Debug info

The debug information is not always complete. I found a hole in it last week. It's been patched now, which is nice, but it was basically a missing piece of debug info that would've made it so that you couldn't know whether you had a bug or the debug info was just not there. That makes it so you can't trust the debugger. I would say overall, though, that the debug info situation seems much improved in comparison to 2022 when apparently it was much less complete (DWARF info, that is, PDB info seems to have been much better overall, historically).

0 - https://odin-lang.org/docs/overview/#type-switch-statement

Post reply on HN