Zig vs Go: constants, variables and basic types
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.