Skip to content

Pointer

Chung Leong edited this page Apr 12, 2024 · 7 revisions

A pointer is a variable that points to other variables. It holds a memory address. It also holds a length if it's a slice pointer.

Auto-deferenecing

Zigar auto-deferences a pointer when you perform a property lookup:

const std = @import("std");

pub const StructA = struct {
    number1: i32,
    number2: i32,

    pub fn print(self: StructA) void {
        std.debug.print("{any}\n", .{self});
    }
};

pub const StructB = struct {
    child: StructA,
    pointer: *StructA,
};

pub var a: StructA = .{ .number1 = 1, .number2 = 2 };
pub var b: StructB = .{
    .child = .{ .number1 = -1, .number2 = -2 },
    .pointer = &a,
};
import module from './pointer-example-1.zig';

console.log(module.b.child.number1, module.b.child.number2);
console.log(module.b.pointer.number1, module.b.pointer.number2);

In the example above, child is a struct in StructB itself while pointer points to a struct sitting outside. The manner of access is the same for both.

Assignment works the same way:

import module from './pointer-example-1.zig';

module.b.child.number1 = -123;
module.b.pointer.number1 = 123;
module.b.child.print();
module.b.pointer.print();
module.a.print();
pointer-example-1.StructA{ .number1 = -123, .number2 = -456 }
pointer-example-1.StructA{ .number1 = 123, .number2 = 456 }
pointer-example-1.StructA{ .number1 = 1, .number2 = 2 }

Notice how a has been modified through the pointer.

Auto-vivification

Assignment to a pointer changes its target:

import module from './pointer-example-1.zig';

module.b.child = { number1: -123, number2: -456 };
module.b.pointer = { number1: 123, number2: 456 };
module.b.child.print();
module.b.pointer.print();
module.a.print();
pointer-example-1.StructA{ .number1 = -123, .number2 = -456 }
pointer-example-1.StructA{ .number1 = 123, .number2 = 456 }
pointer-example-1.StructA{ .number1 = 1, .number2 = 2 }

While the assignment to child altered the struct, the assignment to pointer actually changed the pointer's target to a new instance of StructA, created automatically by Zigar when it detected that the object given isn't an instance of StructA. It's equivalentt to doing the following:

module.b.pointer = new StructA({ number1: 123, number2: 456 });

Explicitly dereferencing

In order to modify the target of a pointer as a whole, you'd need to explicitly deference the pointer:

import module from './pointer-example-1.zig';

module.b.pointer['*'] = { number1: 123, number2: 456 };
module.a.print();

The above code is equivalent to the following Zig code:

b.pointer.* = .{ .number1 = 123, .number2 = 456 };
a.print();

In both cases we're accessing '*`. JavaScript doesn't allow asterisk as a name so we need to use the bracket operator.

Explicity dereferencing is also required when the pointer target is a primitive like integers:

pub var int: i32 = 123;
pub var int_ptr = ∫
import module from './pointer-example-2.zig';

console.log(module.int_ptr['*']);
module.int_ptr['*'] = 555;
console.log(module.int);
module.int_ptr = 42;
console.log(module.int);
123
555
555

You can see once again here how assignment to a pointer changes its target (int was not set to 42).

Certain operations that use Symbol.toPrimitive would trigger auto-defererencing of primitive pointers:

import module from './pointer-example-2.zig';

console.log(`${module.int_ptr}`);
console.log(Number(module.int_ptr));
console.log(module.int_ptr == 123);
123
123
true

Pointer function arguments

const std = @import("std");

pub const File = struct {
    name: []const u8,
    data: []const u8,
};
pub const Directory = struct {
    name: []const u8,
    entries: []const DirectoryEntry,
};
pub const DirectoryEntry = union(enum) {
    file: *const File,
    dir: *const Directory,
};

fn indent(depth: u32) void {
    for (0..depth) |_| {
        std.debug.print("  ", .{});
    }
}

fn printFile(file: *const File, depth: u32) void {
    indent(depth);
    std.debug.print("{s} ({d})\n", .{ file.name, file.data.len });
}

fn printDirectory(dir: *const Directory, depth: u32) void {
    indent(depth);
    std.debug.print("{s}/\n", .{dir.name});
    for (dir.entries) |entry| {
        switch (entry) {
            .file => |f| printFile(f, depth + 1),
            .dir => |d| printDirectory(d, depth + 1),
        }
    }
}

pub fn printDirectoryTree(dir: *const Directory) void {
    printDirectory(dir, 0);
}
import { printDirectoryTree } from './pointer-example-3.zig';

printDirectoryTree({
    name: 'root',
    entries: [
        { file: { name: 'README', data: 'Hello world' } },
        {   
            dir: { 
                name: 'images',
                entries: [
                    { file: { name: 'cat.jpg', data: new ArrayBuffer(8000) } },
                    { file: { name: 'lobster.jpg', data: new ArrayBuffer(16000) } },
                ]
            }
        },
        { 
            dir: {
                name: 'src',
                entries: [
                    { file: { name: 'index.js', data: 'while (true) alert("You suck!")' } },
                    { dir: { name: 'empty', entries: [] } },
                ]
            }
        }
    ]
});
root/
  README (11)
  images/
    cat.jpg (8000)
    lobster.jpg (16000)
  src/
    index.js (31)
    empty/

Auto-casting

Clone this wiki locally