How to return a C string from []u8

I have a function that returns a []u8 array with text content. How can I pass that data to a C function that requires const char * as argument?

1 Like

Hi @jailop, welcome to ziggit!

I edited your post, I think you meant []u8?

I think you are looking for std.mem.Allocator.dupeZ to convert []u8 to [:0]u8 and then you can use .ptr on that to pass it to C.

const c_str = try allocator.dupeZ(str);
defer allocator.free(c_str);
c_function(c_str.ptr);

Also take a look at this, for more details:

1 Like

It worked! Thanks so much @Sze

4 Likes

its actually hard to find the right libraries and functions to get the functionality we need. it would have never crossed my mind that the function to convert an array type into another array type would be in std.mem

1 Like

Yeah, but to be fair I can’t really think of any better place it could be unless a “string” namespace was added

1 Like

Yeah, std.mem has a surprising amount of functionality in it that probably shouldn’t be there. There are plans to audit the standard library and I imagine that’s part of the concern.

2 Likes

I think if you think about this way it makes sense:

  • you need to allocate memory to be able to put a zero at the end of the string so it can be passed to C
  • how do you allocate memory? you look in std.mem to find std.mem.Allocator
  • now that you got an allocator, what methods does it have? Oh there is dupeZ
  • Z is a convention that stands for “give me something zero terminated”

I think there will always be ways that things like the above points are intuitive to some people, while being obscure to others, especially when you aren’t familiar with it yet. Also the points above probably only become clues in hindsight, I think I only found those after I already knew about the dupeZ function.

Maybe the layout of the standard library can be significantly improved, I just don’t know by how much, seems to me that there will always be things, where you just need to learn it, from some learning material or somebody who has already found it.

Even from this forum, I have noticed that it can be difficult to find some topic I remember having read 3-4 months ago and then find a search term to find the topic again, so I think it is no wonder that we get similar questions repeatedly, I think maybe over time with linking between topics, things will become more interconnected and easier to find.

I think one thing that could help in the future was if the standard library came with a guide that is actual free flowing text, with different chapters explaining things and linking to things.
But creating that, will probably be a lot of work and thus it makes sense to first shuffle around where things are located and how the standard library is designed.

Hopefully a lot of this design reasoning would also be explained in this documentation, so that you can understand why things are the way they are, without having to read the entire commit history. For that I think it would also make sense to have a section that describes some of the principles used in deciding how to organize things, at least for things that are done in similar ways repeatedly.

I think one example that does this fairly well is the racket documentation that has a guide and a reference, where the guide explains things and the reference mostly shows pure reference documentation with sometimes short usage examples.

But I am also curious how Zig documentation will develop, towards those more explanation related things.

2 Likes

yes good documentation is important, it gives a warm sense of security and safety, knowing that if your stuck, you can get the information you need from the docs, not saying that the docs are terrible for zig, but its still a young programming language with a small ecosystem, compared to the more established, longer standing programming languages

1 Like

but we live in the age of GPT XD, so those are my main source of information when im stuck, and most of the time its actually correct info and help me out

Here is my final code:

export fn callback(s: *c.Cstring, request: [*c]u8, len: usize) void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const result = query.product_query(allocator, request[0..len]);
    if (result) |value| {
        c.cstring_append(s, value.ptr);
        allocator.free(value);
    }
}

And this is the C function definition where the data is pased from Zig to C:

void cstring_append(Cstring *s, const char *str);

At the end, I didn’t use dupeZ because I added as part of my program a zero sentinel at the end of the slice. Of course, I was able to do that after checking the source code of the dupeZ function.

What this callback function is doing is processing a request received from a C program. The result is an string which is appendend to C custom string struct (struct {char *data; int len; int cap}).
In that way, the caller program manages its heap memory allocations and this Zig code manages its.

This my first project in Zig. I’m so excited to be falling in love with Zig.

Thanks for your help.

2 Likes

One thing you may want to experiment with is whether you can put the gpa into a scope with a bigger lifetime, (I haven’t measured this, but it might be a good idea to measure it) I think creating a gpa on every call to callback might bring quite some overhead (not completely sure).

Also depends on whether you just call it once or many times.

If you decide to put it in a bigger scope I guess you could add a shutdown function:

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
export fn shutdown() void {
    gpa.deinit();
}
export fn callback(s: *c.Cstring, request: [*c]u8, len: usize) void {
    const allocator = gpa.allocator();
    const result = query.product_query(allocator, request[0..len]);
    if (result) |value| {
        c.cstring_append(s, value.ptr);
        allocator.free(value);
    }
}

I am just brainstorming here, I think without the context of the actual program and possibly taking measurements, maybe it isn’t really needed, just something to think about.

But I guess if the program just exits after calling shutdown, calling it might not be strictly necessary / useful. (It could be useful for debugging purpose to catch memory leaks)