December 24, 2025, 1:39am 1
Hello Everyone,
newbie here coming to zig from a high level language like python and I am trying to concatenate strings at the runtime using an allocator and have the below function, however the call is running into error{OutOfMemory}![]u8.
const std = @import("std");
fn cocat_strings(str1: []const u8, str2: []const u8) u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
const combined = std.mem.concat(allocator, u8, &.{ str1, str2 });
defer allocator.free(combined);
return combined;
}
the caller looks like below
pub fn main() void {
const name: *const [3:0]u8 = "Som";
cocat_strings(name, name); // looking for a result like "SomSom"
}
Any help with some explanation w…
December 24, 2025, 1:39am 1
Hello Everyone,
newbie here coming to zig from a high level language like python and I am trying to concatenate strings at the runtime using an allocator and have the below function, however the call is running into error{OutOfMemory}![]u8.
const std = @import("std");
fn cocat_strings(str1: []const u8, str2: []const u8) u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
const combined = std.mem.concat(allocator, u8, &.{ str1, str2 });
defer allocator.free(combined);
return combined;
}
the caller looks like below
pub fn main() void {
const name: *const [3:0]u8 = "Som";
cocat_strings(name, name); // looking for a result like "SomSom"
}
Any help with some explanation would be really appreciated. Also, if someone can outline a few good resources on strings in zig that will be great.
Cheers, DD.
kalapalo December 24, 2025, 1:53am 2
In cocat_strings you use defer allocator.free(combined); that is executed when the scope (your function) exists.
What is expected is that you return the string you have just created. Your function should look something like:
fn concat_strings(str1: []const u8, str2: []const u8) []const u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const combined = std.mem.concat(allocator, u8, &[_][]const u8{ str1, str2 }) catch unreachable;
return combined;
}
2 Likes
in addition to this I can also take an allocator: std.memory.Allocator as an argument and free anddeinit outside of the function.
Your solution has worked , cheers.
kalapalo December 24, 2025, 3:01am 4
Yeah, you can. Or you do your module to handle strings and you initialize an allocator for the whole module. Strings are a good exercise for newbies like us to to get acquainted to the language. Start with:
pub const String = struct {
buff: []u8,
pub fn init(buff_size: usize) String {
}
}
and write hundreds of functions to handle strings: concatenation, converting to and from numbers, to and from cstrings, writing to and from file … for some you will want to use allocator.realloc.
vulpesx December 24, 2025, 3:04am 5
Generally don’t do this.
2 Likes
Zambyte December 24, 2025, 4:27am 6
The return type of std.mem.concat is (in this case) error{OutOfMemory}![]u8. You declare combined as the direct result of this function call, meaning the type of combined is error{OutOfMemory}![]u8. You then pass combined to free, but that fails because free doesn’t expect an error union like that.
You should handle the possible error from calling concat by either using catch to handle the error directly, try to return the error (equivalent to catch |err| return err), or use if/else to conditionally handle the error. Likely you want to use try.
5 Likes
andrewrk December 24, 2025, 7:59am 7
If you’re doing a bunch of string manipulation it’s a good use case for arena allocation. It will help you reduce how many resources you are juggling from many to 1. Start with just one big arena for everything - you can initialize it in main(), and then if you end up needing to free any of that stuff before program exit, then you can introduce more memory management. If you’re just making a CLI tool you probably don’t need anything beyond a single arena allocator for 100% of your memory allocations over the life of the entire program.
6 Likes
Hey :), This is the corrected version of your program, the first thing that has changed is the signature of the function, although not mandatory, it’s usually recommended for both clarity and consistency to have each function that requires memory allocation to accept an allocator parameter, even if you don’t care about being idiomatic, it’s still convenient to be able to pass any allocator.
Second the function signature was incorrect, you see concat can return an error, and a string in Zig is just a slice of u8, therefore the correct return type should be ![]u8, lastly in the main function I show you one way you can transform a ![]u8 into a []u8 which is done by catching the error path and handling it.
const std = @import("std");
fn cocat_strings(allocator: std.mem.Allocator, str1: []const u8, str2: []const u8) ![]u8 {
return try std.mem.concat(allocator, u8, &.{ str1, str2 });
}
pub fn main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const name = "Som";
const my_string = cocat_strings(allocator, name, name) catch |err| {
return std.debug.print("Fatal : {}\n", .{err});
};
defer allocator.free(my_string);
std.debug.print("result : {s}\n", .{my_string});
}
1 Like
tholmes December 24, 2025, 9:44am 9
If you’re okay with potentially losing non-printable characters, you can use std.fmt.allocPrint:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const name: []const u8 = "Som";
const name_concat = try std.fmt.allocPrint(allocator, "{s}{s}", .{name, name});
defer allocator.free(name_concat);
std.log.info("Concatenated name: {s}", .{name_concat});
}
1 Like
Hey Andrew,
thanks for the reply, I was thinking on the same line and end up with GPA being the base allocator and arena being the main allocator like below. I did not share it here before as I do not want to confuse newbies like me.
const std = @import("std");
fn cocat_strings(allocator: std.mem.Allocator, str1: []const u8, str2: []const u8) error{OutOfMemory}![]const u8 {
return try std.mem.concat(allocator, u8, &.{ str1, str2 });
}
pub fn main() !void {
const name: *const [3:0]u8 = "Som";
const direct_slice: []const u8 = "Test";
const name_slice = name[0..];
// create base allocator to do actual work
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const base_allocator = gpa.allocator();
// create arena allocator to reuse the same memory
var arena = std.heap.ArenaAllocator.init(base_allocator);
defer _ = arena.deinit();
const allocator = arena.allocator();
var combined = try cocat_strings(allocator, name, name);
std.debug.print("concat string: `{s}`\n", .{combined});
combined = try cocat_strings(allocator, name_slice, direct_slice);
std.debug.print("concat2 string: `{s}`\n", .{combined});
}
Cheers, DD.
andrewrk December 24, 2025, 8:03pm 11
concat(allocator, u8, name, name) is shorter than cocat_strings(allocator, name, name). You could just const concat = std.mem.concat; rather than defining the cocat_strings function.
2 Likes
Validark December 24, 2025, 8:28pm 12
Come on. Don’t give a newbie a catch unreachable. Nobody should use that feature until they actually know what they’re doing.
3 Likes
andrewrk December 24, 2025, 9:36pm 13
Agreed. It’s also clearly and obviously incorrect here.
Shoutouts to myself seven years ago
1 Like
kalapalo December 24, 2025, 9:41pm 14
You’re right, I admit.
Should have ![]const u8 as return type. Allocation may fail due to bugs, hardware problems, OS not to keep its promise, …
kalapalo December 24, 2025, 9:46pm 15
Hope your metric applies to me
Excited to know that in 7 years I will be sharp.
Reading the quality ziggit answers make feel pretty dumb sometimes (most of the time ?).
1 Like
Validark December 24, 2025, 9:52pm 16
You can also just run out of memory if you use it all.
Validark December 25, 2025, 12:21am 17
I actually still send people that talk!