dwyl / learn-zig

🦎 Learn the Zig programming language for fun!
GNU General Public License v2.0
4 stars 0 forks source link

Ziggy String literals, arrays, slices & WFT comptime #8

Open ndrean opened 1 month ago

ndrean commented 1 month ago

https://zig.news/ is a good place to search on ziggy problems.

Explore types with @TypeOf

[building...]

Vademecum

const std = @import("std");
const print = std.debug.print;

// my String type
const String: type = []const u8;

// function returning a type
fn isString() type {
    return String;
}

test "What?" {
     // a String literal is a const pointer to an array of characters
     const elixir = "Elixir";
     print("\nThe variable with value {s} is a string literal. Check the type: {}\n", .{ elixir, @TypeOf(elixir) });

    var zig: String = "Zig";
    zig = "zig"
    std.debug.print("\nThe variable with value {s} is a String: {}\n", .{ zig, @TypeOf(zig) == isString() });

    const ptr_elixir = &elixir;
    const ptr_zig = &zig;

    print("\n{}\t {}\t {s}\n", .{ @TypeOf(ptr_elixir), @TypeOf(ptr_elixir.*), ptr_elixir.* });
    print("\n{}\t {s}\n", .{ @TypeOf(ptr_zig.*), ptr_zig.* });

    try std.testing.expect(@TypeOf(l) == isString());

    const word = [5]u8{ 'h', 'e', 'l', 'l', 'o' };
    const slice_word = word[0..];
    print("\nArrays: {s}\t {}\t {s}\t {}\n", .{ word, @TypeOf(word), slice_word, @TypeOf(slice_word) });

}

Run zig test my_file.zig:

The variable with value Elixir is a string literal. Check the type:  *const [6:0]u8

The variable with value zig is a String? : true

*const [6:0]u8   Elixir
^^ the pointer is pointing to a const pointer pointing to an array of characters (1 byte, 8 bites)

*[]const u8  []const u8  Zig
#   ^^ if we use "const zig = "Zig", then we get @TypeOf(&zig) == *const []const u8
# the type determines the presence of the first "const"

Arrays: hello    [5]u8   hello   *const [4]u8

All 1 tests passed.

Lets summarise

const word = [5]u8{'h', 'e', 'l', 'l', 'o'} with type [5:0]u8

For example, when you allocate memory for n elements of type T, we get a slice: var slice_of_t: []T = allocator.alloc(T, n).

const slice_word = word[0..] with type *const [4]u8

const zig = "zig" has type *const [3:0]u8

Ok, now we test our understanding:

fn printIt(msg: []const u8) void {
    print("{s}", .{msg});
}

fn testIt() [5]u8 {
    return [5]u8{ 'h', 'e', 'l', 'l', 'o' };
}

We can use it like this:

test "print" {
   printIt("zig")

   printIt(&testIt());
            ^^
}

printIt is expecting a slice, []const u8, which is a pointer.... "zig" has type *const [3:0]u8, a pointer, ✅ since testIt() returns an [5]u8, an array, we need to pass &testIt()

Now, can we mutate a literal string? Yes, dereference it!

test "mut literal" {

   const Zig = "Zig";
   var v_zig = Zig.*;
   print("\n{}\t{s}\n", .{ @TypeOf(v_zig), v_zig });

   v_zig[0] = 'P';
   print("\n{}\t{s}\n", .{ @TypeOf(v_zig), v_zig });
   std.testing.expectEqualStrings("Pig", &v_zig);

gives:

[3:0]u8 Zig

[3:0]u8 Pig

All 1 tests passed.

Structs

Now consider using a struct. Pointers are needed here when you use functions to modify.


const Person = struct { salary: i16};

fn increase(p: *Person, amount: i16) void {
              ^^
   p.salary += amount;
}

test "struct" {
   var me = Person{ .salary = 100};
   std.debug.print("{}", .{me.salary});

   increase(&me, 100);
            ^^
   std.debug.print("{}", .{me.salary});

   me.salary += 100;

   std.debug.print("{}", .{me.salary});

  try std.testing.expectEqual(me.salary, 300);
}

We get:

100
200
300
All 1 tests passed.

❗ We need to use a pointer to pass the struct Person to the function "increase". Indeed, the arguments of a function are immutable, so inside the function, we can't modify the p we get. => we get: "error: cannot assign to constant" When we pass a pointer, the pointer is not modified inside the function. However, we can mutate the data at which the pointer is pointing at. We don't need to deference the pointer: p.salary works fine.

Now consider a struct that holds internal methods. Again, when the method modifies one of the fields of the struct, you must use a pointer. Note that the inner method uses a reflection.

const Person = struct {
    salary: i16,
    fn double(p: *Person) void {
        p.salary *= 2;
    }
};

test "struct" {
   var me = Person{ .salary = 100 };
    std.debug.print("\n{}\n", .{me.salary});

    increase(&me, 100);
    std.debug.print("\n{}\n", .{me.salary});

    me.double();
    std.debug.print("\n{}\n", .{me.salary});

    me.salary += 100;
    std.debug.print("\n{}\n", .{me.salary});

    try std.testing.expectEqual(me.salary, 500);
}

We get as expected:

100

200

400

500

All 1 tests passed.

Pause

Check this video:

Screenshot 2024-10-21 at 13 59 09

WFT Comptime....

❗ References to keep!

A first link:

Screenshot 2024-10-21 at 12 57 13

Then, a GitHub link:

Screenshot 2024-10-21 at 12 58 21

ndrean commented 3 weeks ago

Arrays: constant or mutable

const std = @import("std");

// the input is a pointer to CONSTANT values; this is valid
fn sum(input: []const u8) u8 {
    var sum: u8 = 0;
    for (input) |e| sum += e;
    return sum;
}

// we add the index to each element of the array
// the argument is a MUTABLE slice
fn mut(input: []u8) []u8 {

    // this is not possible: "error: cannot assign to constant"
    // for (input, 0..) |e: u8, i: usize| {
    //   e += i;
    // }

    // instead, this is valid
    for (input, 0..) |_, i: usize|  {
       input[i] += 1;
    }

    // alternative
    for (input, 0..) |*v: **u8, i: usize| {
       v.* += i;
    }

   return input;
}

test "arrays" {
    const cst_input = [_]u8{ 1, 2, 3, 4, 5 };
    const sum = sum1(&cst_input);
    try std.testing.expectEqual(sum, 15);

    // we define a mutable array.
    var var_input = [_]u8{ 1, 2, 3, 4, 5 };
    // const mutInput = mut(&cst_input);  <- this fails
    const mutInput = mut(&var_input);
    try std.testing.expectEqualSlices(u8, mutInput, &[_]u8{ 2, 3, 4, 5, 6 });
 }

This is similar to when you want to mutate data via its pointer (more involved, yes...)

// mutate via the pointer
fn db_ptr(x: *u8) void {
    x.* *= 2;
}

// just return another value
fn db(x: u8) u8 {
    return x * 2;
}

test "mut" {
    var x: u8 = 1;
    db_ptr(&x);
    try std.testing.expectEqual(x, 2);

    var y: u8 = 1;
    y = db(y);
    try std.testing.expectEqual(y, 2);
}