You’ve marked dst as const, which means that the data it contains is immutable. In this case, since its value is comptime-known, that allows Zig to place it in the “global read-only data” section of the binary. Trying to write to this data will trigger a bus error.
You’re explicitly bypassing the type system’s safety here with @constCast, which converts the *const [128]u8 to a *[128]u8 – you’re telling the type system “don’t worry, this data is actually mutable, you just don’t know that for some reason”. You should never use @constCast unless you are certain the underlying memory really is mutable.
The allocator version works because dst is then a pointer to heap-allocated mutable memory. The fact that this is still const dst doesn’t matter, because dst in this version is a pointer, which you aren’t trying to mutate; you’re just mutating what the pointer refers to, which is heap-allocated and thus mutable at runtime.
The correct version of your code would look like this:
const std = @import("std");
const c = @cImport({
@cInclude("impl.c");
});
pub fn main() !void {
const src = "hello world";
var dst = [_]u8{0} ** 128;
const ret = c.my_strlcpy(
@ptrCast(&dst), // i haven't checked whether this cast is necessary but I'm guessing it is
@ptrCast(src.ptr), // no @constCast needed here, the parameter is const anyway!
src.len + 1,
);
std.debug.print("result: {}\n", .{ret});
std.debug.print("result: {s}\n", .{dst});
}
It is a bit confusing, but you’ll get the hang of it.
All of these are valid signatures:
const a: []const u8 = doThing();
var b: []const u8 = doThing();
const c: []u8 = doThing();
var d: []u8 = doThing();
// function signature:
fn doThing() []u8 { ...
The variables which are const, cannot be reassigned. Slices have two fields, .ptr and .len, if the variable is const, you can’t change the value of those fields, and you can’t reassign the whole variable to another slice.
The types which are const, point to memory which you aren’t allowed to mutate. For a and b this is illegal:
Correct, but this makes sense: an array is a value, and values are either const or var, either mutable or not so.
You can coerce an array into a slice: that creates a struct with a pointer to the head of the array, and with its length as the .len field. If the array is a variable, that slice can be either []const u8 or []u8, if the array is const, then the slice is automatically a []const u8.
So you can create a “look but don’t touch” view into a mutable array, but const alt_message: [5]const u8 would be redundant. Moreover it doesn’t make sense, because const in a type, as the error message says, makes a const pointer, and an array isn’t a pointer, of which Zig has quite a few varieties, including slices.
There have been proposals kicked around for things like a var which can be modified but not reassigned, or for fields in a struct to have const-ness even when the struct is a var, but those complicate the language more than they’re worth†. The type system we have is already fairly complex, I would say only in necessary ways which make it pleasant to work with, but it’s a lot to learn, and cutting even finer distinctions into it would multiply that.
†Since *const T and []const T are types, structs can and do have fields of those types. What you can’t do is have e.g. a u32 field which is constant even when the struct is a var. Just clarifying what I mean there.