How to go from extern pointer to slice?

I am learning zig, and focused on using it for web assembly. I know wasm well, I have written programs by writing wat by hand, and also compiling from C.
I am currently trying to expose zig’s memory allocator to the wasm host (i.e. javascript) this is basically the same as making a zig library that can be called from C.

I’m not sure if this is correct but zig compiles,

const allocator = @import("std").heap.page_allocator;

export fn alloc (length: u32) *[]const u8 {
  var ptr = allocator.alloc(u8, length) catch unreachable;
  return &ptr;
}

then I want to be able to pass a pointer back in from js/C and free it in zig.

export fn free (ptr:*[]const u8) void {
  allocator.free(ptr);
}

that tells me

/usr/lib/zig/std/mem.zig:2755:9: error: expected []T or *[_]T, passed *[]const u8
        @compileError("expected []T or *[_]T, passed " ++ @typeName(sliceType));

I tried dereferencing the pointer

export fn free (ptr:*[]const u8) void {
  allocator.free(*ptr);
}

but that seems wrong to me because I was successfully passing bytes back and forth using [*]const u8 I havn’t found a reference that explains the different variations on * etc

but that gives

./malloc.zig:9:19: error: expected type 'type', found '*[]const u8'
  allocator.free(*ptr);

I think the solution must be to convert my pointer into a slice.
Since a slice is an array and a usize - but I couldn’t find any method that creates a slice from a pointer and a size.

some progress: I successfully made code that allocates a struct with a slice inside of it.

const allocator = @import("std").heap.page_allocator;

const SliceWrap = struct {
    slice: []u8,
};

export fn create () *SliceWrap {
  var ptr = allocator.create(SliceWrap) catch unreachable;
  ptr.slice = allocator.alloc(u8, 5) catch unreachable;
  ptr.slice[0] = 'h';
  ptr.slice[1] = 'e';
  ptr.slice[2] = 'l';
  ptr.slice[3] = 'l';
  ptr.slice[4] = 'o';
  return ptr;
}

calling create returns a pointer, then dumping the memory at that address:

<Buffer 00 00 03 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>

which I guessed would be a pointer + length. reading that gave:

<Buffer 68 65 6c 6c 6f>

that’s ‘hello’!

okay I think I get it now. I was using c style pointer dereference syntax, which means something different in zig.

export fn create_slice(len: usize) *[]u8 {
  return &(allocator.alloc(u8, len) catch unreachable);
}

export fn free_slice (ptr: *[]u8) void {
  allocator.free(ptr.*);
  return;
}

also I’ve figured out that a slice is {ptr, len} pair, so a pointer to a slice is passed as a single 64 bit value, the bottom half being a 32 bit pointer, and top half being a 32bit length. but does this mean that when I alloc a slice and return it’s address: &(allocator.alloc(u8, len) catch unreachable) it’s actually two separate memory ranges - the slice content, and the slice header {ptr,len}… does this mean that the slice header is actually on the stack? (and so I shouldn’t really be returning pointers to it like this?)

however, if I create a struct with a slice in it, I allocated that separately, clearly on the heap.
but how to create a slice from {ptr,len} ?

I figured it out.

const expect = @import("std").testing.expect;

fn slicify(ptr: [*]u8, len:usize) []u8 {
  return @ptrCast(*[]u8, &.{.ptr=ptr, .len=len}).*;
}

test "convert array to slice" {
  var array = [_]u8 {1,2,3};
  var slice = slicify(&array, array.len);
  try expect(slice[1..][1..][0] == 3);
  try expect(slice.len == 3);
  try expect(slice[1..].len == 2);
  try expect(slice[2..].len == 1);
 try expect(slice[1..][1..][0] == 3);
 }

I could not find a function like slicify in std lib, but how it works is it reconstructs the slice in memory then recasts a pointer to it. This i think is necessary for when you might receive calls from c that are an array + length (as is quite common in c)

in this case, I am testing it with a array literal which has a length known ahead of time.
I’m pretty sure that this will break zig’s safety properties, so you’ll need to be confidant that any code that calls this is correct.

1 Like

Maybe I do not have full context of the problem, but you can bypass the slicify function with just slicing syntax

const expect = @import("std").testing.expect;

test "convert array to slice" {
    var array = [_]u8{ 1, 2, 3 };
    var ptr = @ptrCast([*]u8, &array); // this cast isn't necessary, but just to show it works with [*]u8

    var slice = ptr[0..array.len];
    try expect(slice[1..][1..][0] == 3);
    try expect(slice.len == 3);
    try expect(slice[1..].len == 2);
    try expect(slice[2..].len == 1);
    try expect(slice[1..][1..][0] == 3);
}

Right now I’m doing a lot of interop with C code, and I just slice my [*]u8 pointers with the given lengths to create Zig slices. You are correct though, that things might break if the length is incorrect!

2 Likes

yes, it works because a zig array literal has a size known at compile time.
I couldn’t figure out an easy way how to create a array pointer that had a length unknown to zig.
so I just faked it using an array.

but if this was a cb from C it would just be c array, which wouldn’t have length. I’m still new here but I think zig would refuse to use slice syntax on that because it wouldn’t know how long it was. (unless it had an end sentinel, which is more c style, certainly with strings but not necessarily with arrays)

When working with C code, you would likely have one of *T, [*]T, [*c]T for pointers. The first being a single-item pointer cannot be sliced, but the other two types can be sliced even though the length is not runtime known. See the docs on pointers and C pointers.

AFAIK, slicing a slice will check for out-of-range at runtime, but slicing a many-item pointer does not do checks, but Zig will still allow the slicing to make it easier to iterate over the pointer and take advantage of other Zig features.

I just confirmed this myself with an exported function I wrote a while back, the following (simplified) code works fine when called via LuaJIT ffi:

export fn rankItem(tokens: [*][*:0]const u8, num_tokens: usize) f64 {
    for (tokens[0..num_tokens]) |t| {
        const token = std.mem.span(t);
        // do something with token
    }
    // omitted
}

I’m not a Zig expert, but I do hope this helps :slight_smile:

1 Like

hmm, does using slice syntax on unknown many pointer return a valid slice?

fn slicify2(ptr: [*]u8, len:usize) []u8 {
  return ptr[0..len]
}

aha, it does!!! (put it through my tests) now I understand why it’s not in the standard lib because it’s trivial! but I did spend a lot of time searching the documentation to try and understand how to do this!

2 Likes

Glad you got it figured out!

but I did spend a lot of time searching the documentation to try and understand how to do this!

In your defense, the docs on slicing are pretty minimal :slight_smile: One of the downsides of using an unfinished language!

1 Like

yes, but the upside is that the people who choose to use an unfinished language are more interesting!

2 Likes

I’m a bit late to the party but this is a useful read to learn how to convert between pointers, slices and arrays: Beginner's notes on Slices/Arrays/Strings - Zig NEWS

4 Likes