Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ndrean opened this issue Oct 21, 2024 · 1 comment
Open

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

ndrean opened this issue Oct 21, 2024 · 1 comment

Comments

@ndrean
Copy link

ndrean commented Oct 21, 2024

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

  • Arrays are [n]T of comptime known length. You can instantiate them:

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

  • Slices are []T of types T with runtime known length. They are made of a pointer to the first element of an Array and a length.

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

  • You can instantiate a slice from an array: we check that we get a pointer

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

  • Recall that the type of a string literal is a pointer to an array.

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 ndrean changed the title Ziggy types, WFT comptime Ziggy types, pointers & WFT comptime Oct 21, 2024
@ndrean ndrean changed the title Ziggy types, pointers & WFT comptime Ziggy String literals, arrays, slices & WFT comptime Oct 22, 2024
@ndrean
Copy link
Author

ndrean commented Oct 28, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant