logo

Zig Programming Language

Posted on: 2022-12-28

Conclusion

Future:

Good

Interesting

Not sure

Bad

Ugly

Exclusion Summary

Discussion

Has defer, and errdefer. Has option types. Can access the inner value with

fn doAThing(optional_foo: ?*Foo) void {
    // do some stuff

    if (optional_foo) |foo| {
        doSomethingWithFoo(foo);
    }

    // do some stuff
}

Not sure entirely how this works. Here it's clear foo must be associated with optional_foo. On the other hand we new name, and how does this work with multiple variables, or a more complex expression? We perhaps often don't need a new variable name, although in some circumstances you might want to to name either. So...

    if (optional_foo) {
        // can use as contained value, because the if expression proves it hold the value
        doSomethingWithFoo(optional_foo);
    }

    // `as` is probly not right. `reify`, or perhaps some syntax
    if (optional_foo as foo) {
        doSomethingWithFoo(\foo);
    }
    // say. Where => is true if option is true, and if it's true variable foo holds the contained value.
    if (optional_foo => foo) {
        doSomethingWithFoo(\foo);
    }

Is a concrete value coerced into an optional type?

Any function that does memory allocation, takes an allocator. That seems kind of dull in practice. I like the context idea, where it holds an allocator (and perhaps other stuff, like a log etc). This means that the caller doesn't know that the function can consume memory, though. Perhaps that is indicated in some effects system.

Looks like in the syntax uses ! to indicate an error or type. For example

fn charToDigit(c: u8) !u8 {
    const value = switch (c) {
        '0'...'9' => c - '0',
        'A'...'Z' => c - 'A' + 10,
        'a'...'z' => c - 'a' + 10,
        else => return error.InvalidCharacter,
    };

    return value;
}

In the use of this example zig does determine all possible return values (?) and require they are all covered. Not sure what error is yet, if it's an enum then that's simple, but an error system would seem to need to be able to be extendable.

The use of integer types of arbitrary sizes, does give some nice flexibility and control. What seems to be missing is how define a backing. You could get something like this if you could do

struct SomeType : i32
{
    i1 bit;
    i7 otherSuff;
    i16 yetMore;
}

Then if you can make @transparent within use. I think Odin used using for @transparent.

struct OtherType
{
    @transparent SomeType t;
    int otherType;
}

This still might not give the flexibility desired, as doesn't allow a value to span byte/short/word/long etc. Probably covers the majority of scenarios.

Nice to see c_ prefixed type that match up with c ABI.

The use of break to be able to return a value. Seems clunky that it needs a label, but something like that is necesary to indentify where to return to. Perhaps a keyword could be used such it is not necessary to specify. The mechansim is a little like a lambda/closure.

var declarations at the top level or in struct declarations are stored in the global data section.

This explains the odd static variable observation - the var prefix makes it global. It seems odd that var in a function is a stack variable, but perhaps the argument is that the context (function/struct) makes that make sense.

Read:

Has a section around issues with recursion.

Makes claims around safely handling memory exhaustion. Not sure I'm buying it.

Interesting seeing all the targets enumerated from LLVM. Seems to be

On trying out with compiler explorer, doing something simple with shifing. I found

fn someFunc(a : i32) i32 {
    // Doesn't work, because rhs << is u5. Which is not unreasonable per se, but requires 
    return i << a;
}

This worked with...

export fn someFunc(num: i32) i32 {
    return @as(i32, 1) << @truncate(u5, @bitCast(u32, num));
}

Hmm, it's a little convoluted though. Say compared to

int someFunc(int i) { return 1 << (i & 0x1f); }

Links