Returning a string or modifying a string in a function

I’m trying to implement the HIFF function, which requires a recursive subfunction like this one:

pub fn T(ev: []u8, result: []u8) void {
    if (std.mem.eql(u8, ev, "-") or std.mem.eql(u8, ev, "0") or std.mem.eql(u8, ev, "1")) {
        result = ev;
    } else if (std.mem.eql(u8, ev, "00")) {
        result = "0";
    } else if (std.mem.eql(u8, ev, "11")) {
        result = "1";
    } else if (std.mem.eql(u8, ev, "01") or std.mem.eql(u8, ev, "10")) {
        result = "-";
    } else if ((ev.len == 2) and std.mem.indexOf(u32, ev, "-")) {
        result = "-";
    } else {
        result = t(T(ev[0 .. ev.len / 2], result), T(ev[ev.len / 2 .. ev.len], result));
    }
}

Essentially, it looks at a string and produces another, recursing in certain conditions.

Problem is, I can’t figure out how to return the string. This is the last installment, that has included returning u8, using *[u8] as a pass-by-value; in some cases it’s because it’s constant, others it’s another thing.

Essentially I would like to know how to do something like

fun a(b: []u8) []u8 {
   if ( std.mem.eql(u8,b,"foo") {
     return "This is foo";
   else {
    return "This is bar";
   }
}

fn a(b: []u8) []u8 {

Start by returning []const u8 instead. You’re referencing constant string literals, so the return type needs to reflect that if you’re doing direct return "abc". Also, if you take constant string literals by argument, your parameter type needs to be []const u8 as well.

We’d have to see what that other thing is before making more commentary on the code. If you rewrite it correct types for literal values, maybe you can show us those errors that persist after?

I’d start there and then see where it takes you.

1 Like

I’ll add a couple more approaches that may work for your circumstance, but I don’t know what your constraints are so I can only guess. These approaches assume you want to use an out argument.

One approach is to pass in a buffer by pointer and then @memcpy your results out. That may be something like:

const Item = struct {
    data: [5]u8 = undefined,
    last: usize = 0,

    pub fn slice(self: *const Item) []const u8 {
        return self.data[0..self.last];
    }    
};

fn foo(out: *Item) void {
    // some branch condition
        @memcpy(out.data[0..2], "01");
        out.last = 2;
}

The issue with this pattern is the calls to @memcpy which add up over time and you have to get your buffer size correct.

Another pattern is ye ol’ pointer-to-pointer. It’s a much more common pattern in C than it is here in Zig, but by the same token you can edit the above code and make it work quite easily:

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

fn foo(out: *Item) void {
    // some branch condition
        out.slice = "01";
}

Note that the slice itself is not constant - the data it points to is. You can reassign that data and it will overwrite the slice. You can take *[]const u8 as well, it’s just not as common. That looks like:

fn foo(out: *[]const u8) void {
    out.* = "01";
}
1 Like