DEV Community

Cover image for Zig vs Go: constants, variables and basic types
Paolo Carraro
Paolo Carraro

Posted on

Zig vs Go: constants, variables and basic types

(you can find previous post about same topic here)

const and var

Being two statically and strongly typed languages, there are many similarities in the definition of constants and variables, which must therefore be explicitly typed or typed by inference. Let's see some examples

// Go
func main() {
    const myInt int32 = 123
    // typed by inference
    myFloat := 12.34
    fmt.Printf("integer: %d - float: %f\n", myInt, myFloat)
    myFloat = 45.67
    fmt.Printf("integer: %d - float: %f\n", myInt, myFloat)

    // zero value assigned
    var myBoolean bool
    fmt.Printf("boolean: %t\n", myBoolean)
    myBoolean = true
    fmt.Printf("boolean: %t\n", myBoolean)

    // print types
    fmt.Printf("%T - %T - %T\n", myInt, myFloat, myBoolean)
}
Enter fullscreen mode Exit fullscreen mode
// Zig 
pub fn main() !void {
    const myInt: i32 = 123;
    var myFloat = 12.34;
    myFloat = 45.67;
    // typed by interence
    const myBoolean = true;
    std.debug.print("integer: {d} - float: {d} - boolean: {}\n", .{ myInt, myFloat, myBoolean });
    // print types
    std.debug.print("{} {} {}\n", .{ @TypeOf(myInt), @TypeOf(myFloat), @TypeOf(myBoolean) });

    // no zero value in Zig
    var undefinedValue: f32 = undefined;
    std.debug.print("undefined value: {d}\n", .{undefinedValue});
    undefinedValue = 73.123;
    std.debug.print("assigned value: {d}\n", .{undefinedValue});
}
Enter fullscreen mode Exit fullscreen mode

Zig's ability to initialize to undefined aims to optimize memory management to avoid unnecessary initialization costs and depends on how the executable is compiled. You can get safer behaviors where memory is initialized to default values (in debug mode for example) or optimizations where only memory is allocated but on first read what was previously there is read. It is therefore up to the developer to evaluate what is best in the specific case.

Numeric types

Zig, unlike Go, has the ability to indicate how many bytes should be allocated for an integer or real number: for example, we can specify u2 to indicate a positive integer that contains a maximum of 3. In case of normal additions that overflow the type, we get a panic unless we use a particular syntax for operations (for example %+) to handle overflow with wrap around as Go does by default.

// Go
var myNumber uint8 = 255
fmt.Printf("value : %d\n", myNumber)
myNumber += 1
fmt.Printf("value overflow tollerated : %d\n", myNumber)
Enter fullscreen mode Exit fullscreen mode
// Zig
var myNumber: u2 = 3;
std.debug.print("number {d} of type {}\n", .{ myNumber, @TypeOf(myNumber) });
myNumber += 1; // panic error!
Enter fullscreen mode Exit fullscreen mode

String type?

In Zig, one thing that stands out is that there is no string type, but strings are treated as arrays of bytes (u8) that can also become read-only when we insert a hard-coded string since the string becomes part of the compiled binary.
Furthermore, for compatibility with C, arrays or slices always contain a 0 (null terminator, not the representation of 0) at the end used as a sentinel, that is, as a string delimiter. The difference with Go, which hides this complexity (pointer to a slice and a length), in this case is so marked that it's not worth making much of a comparison.

// hard coded string are a pointer of a read only array
const myConstString: *const [14:0]u8 = "hello ziguanas";
std.debug.print("const string {s}\n", .{myConstString});
std.debug.print("len and size of myConstString: {d} - {d}\n", .{ myConstString.len, @sizeOf(@TypeOf(myConstString.*)) });
std.debug.print("first value of string {d} formatted as char \"{c}\"\n", .{ myConstString[0], myConstString[0] });

// create allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var allocator = gpa.allocator();

// allocate memory for contains the string
var myStringAllocation = try allocator.alloc(u8, myConstString.len);
defer allocator.free(myStringAllocation);
// fill allocation copying a const string
@memcpy(myStringAllocation, myConstString);
std.debug.print("allocated string before edit: {s}\n", .{myStringAllocation});
// mutate the string
myStringAllocation[0] = 'H';
std.debug.print("allocated string after  edit: {s}\n", .{myStringAllocation});
Enter fullscreen mode Exit fullscreen mode

Top comments (0)