(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)
}
// 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});
}
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)
// Zig
var myNumber: u2 = 3;
std.debug.print("number {d} of type {}\n", .{ myNumber, @TypeOf(myNumber) });
myNumber += 1; // panic error!
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});
Top comments (0)