Zig vs Go: constants, variables and basic types

Published: (January 14, 2026 at 07:15 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Being two statically and strongly typed languages, Go and Zig share many similarities in how constants and variables are defined. Both require explicit types or rely on type inference.

Constants and Variables

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)
}

Zig

pub fn main() !void {
    const myInt: i32 = 123;
    var myFloat = 12.34;
    myFloat = 45.67;
    // typed by inference
    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});
}

Zig’s undefined initializer avoids unnecessary memory initialization, which can improve performance. In debug builds the memory may be zero‑initialized for safety, while release builds may leave it uninitialized until first read.

Integer Sizes and Overflow

Go (default overflow behavior)

var myNumber uint8 = 255
fmt.Printf("value : %d\n", myNumber)
myNumber += 1
fmt.Printf("value overflow tolerated : %d\n", myNumber) // wraps around to 0

Zig (explicit overflow handling)

var myNumber: u2 = 3;
std.debug.print("number {d} of type {}\n", .{ myNumber, @TypeOf(myNumber) });
myNumber += 1; // panic error! (overflow)

Zig allows specifying the exact bit width of integers (e.g., u2 for a 2‑bit unsigned integer). By default, arithmetic overflow triggers a panic. To obtain wrap‑around semantics similar to Go, Zig provides overflow‑aware operators such as %+.

Strings

Zig does not have a dedicated string type; strings are treated as arrays of bytes (u8). A string literal becomes a read‑only array embedded in the binary.

// hard‑coded string is a pointer to 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] });

Allocating a mutable string

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

// allocate memory for the string
var myStringAllocation = try allocator.alloc(u8, myConstString.len);
defer allocator.free(myStringAllocation);

// copy the constant string into the allocation
@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});

By allocating memory and copying the constant data, the string becomes mutable, allowing in‑place modifications.

Back to Blog

Related posts

Read more »

Zig vs Go: init and run

Initialization In Go we initialize a module with: bash go mod init module-name In Zig the equivalent is simply: bash zig init Zig does not require explicit “mo...