So far the only use of @src() I have seen was together with tracy, but when trying to log a sourcelocation myself I encountered the issue of creating the format tuple manually each time.
There is a std.meta.ArgsTuple function, but no StructTuple or StructFieldsTuple.
Would this be a useful addition to the library and or am I just overseeing obvious issues with this?:
const std = @import("std");
const log = std.log.scoped(.ok);
const src_log = "\n Module: {s}\n File: {s}\n Fn: {s}\n Column {d:<5}\n Row {d:<5}";
/// For a given struct or union type, returns a tuple type which fields will
/// correspond to the field types.
///
/// Examples:
/// - `StructTuple(struct{a: u32})` ⇒ `tuple { u32 }`
/// - `StructTuple(struct{a: u32, b: f16})` ⇒ `tuple { u32, f16 }`
pub fn StructTuple(comptime Struct_: type) type {
const field_info = switch (@typeInfo(Struct_)) {
.@"struct" => |info| info.fields,
else => @compileError("StructTuple expects a struct type"),
};
var struct_field_list: [field_info.len]type = undefined;
inline for (field_info, 0..) |field, i| {
struct_field_list[i] = field.type;
}
return std.meta.Tuple(&struct_field_list);
}
fn format_src(comptime src: std.builtin.SourceLocation) StructTuple(std.builtin.SourceLocation) {
return .{
src.module,
src.file,
src.fn_name,
src.line,
src.column,
};
}
fn dummy() void {
log.warn(src_log, format_src(@src()));
std.debug.print("Hi\n", .{});
}
pub fn main() !void {
dummy();
}
Is the order of fields in @typeInfo(T).@"struct".fields being the same as declaration order considered a language guarantee, other than for tuples?
Because, I could plausibly imagine a scenario where it changes in future to reflect layout in memory, for example. I guess even for tuples it wouldn’t be strictly necessary.
I believe reordering structs for alignment-based memorysavings only takes place in some later step during compilation, meaning that layout-order should be preserved for meta-programmin. But defenitely not sure.
since SourceLocation is a struct you can just std.debug.print(src_log, @src()); format just uses the order that it gets from @typeInfo which is the order they are defined in, (i recall a discusion about whether or not that is garunteed by the language, i dont remember the result but this is how it works atm)
if you want to change the order you can refer to the field names in the format string eg "{[module]s}", if its a tuple the field names are just their index
But although a tuples field names are just their indexes, I cannot access them using @fields. I can compare both the @typeInfo fieldnames with my constructed fieldnames ( using std.mem.eql and std.fmt.comptimePrint) but once I attempt to use said fieldname with @fields, i get the error of error: struct 'struct { [:0]const u8, [:0]const u8, [:0]const u8, u32, u32 }' has no member named '0'
strange, wen doing things manually on normally constructed tuples, it works:
Thank you for engaging with my post, but I found the solution. I created a type instead of an undefined instance of a type. Functional code below.
Remaining question is only if my Function StructFieldsTuple and tupleFromStructis useful enough to be included in std.meta for general usage.
const std = @import("std");
const log = std.log.scoped(.ok);
const src_log = "\n Module: {s}\n File: {s}\n Fn: {s}\n Column {d:<5}\n Row {d:<5}";
/// For a given struct or union type, returns a tuple type which fields will
/// correspond to the field types.
///
/// Examples:
/// - `StructFieldsTuple(struct{a: u32})` ⇒ `tuple { u32 }`
/// - `StructFieldsTuple(struct{a: u32, b: f16})` ⇒ `tuple { u32, f16 }`
pub fn StructFieldsTuple(comptime Struct: anytype) type {
const field_info = switch (@typeInfo(@TypeOf(Struct))) {
.@"struct" => |info| info.fields,
else => @compileError("StructFieldsTuple expects a struct type"),
};
var struct_field_list: [field_info.len]type = undefined;
inline for (field_info, 0..) |field, i| {
struct_field_list[i] = field.type;
}
return std.meta.Tuple(&struct_field_list);
}
fn tupleFromStruct(comptime Struct: anytype) StructFieldsTuple(Struct) {
var fields_tuple: StructFieldsTuple(Struct) = undefined;
const field_info = switch (@typeInfo(@TypeOf(Struct))) {
.@"struct" => |info| info.fields,
else => @compileError("StructFieldsTuple expects a struct type"),
};
inline for (field_info, 0..) |field, i| {
@setEvalBranchQuota(10_000);
const name_i = std.fmt.comptimePrint("{d}", .{i});
@field(fields_tuple, name_i) = @field(Struct, field.name);
}
return fields_tuple;
}
fn dummy() void {
log.warn(src_log, tupleFromStruct(@src()));
}
pub fn main() !void {
dummy();
}
I don’t think it’s useful. I’d argue if you needed this, you’re probably doing something wrong. There are ofc exceptions, but I think the legitimate use case for this is very niche
Also note that with tuples or the original struct, the column and row end up being wrong, because they are printed in the opposite order they are declared in.
With using field name specifiers in the format string it is much harder to mess that up.