diff --git a/.gitignore b/.gitignore index 474fa3e..aad45bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .direnv/ .zig-cache zig-out +.DS_Store diff --git a/build.zig b/build.zig index a55bd38..87840e6 100644 --- a/build.zig +++ b/build.zig @@ -9,26 +9,34 @@ pub fn build(b: *std.Build) !void { "add apple SDK paths from Xcode installation", ) orelse true; + // Translate the Objective-C runtime headers once in the build so the Zig + // code can import a stable generated module instead of invoking @cImport + // from every compile. + const objc_c = try translateCModule(b, target, optimize); + const objc = b.addModule("objc", .{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); + objc.addImport("objc-c", objc_c); if (add_paths) try addAppleSDK(b, objc); objc.linkSystemLibrary("objc", .{}); objc.linkFramework("Foundation", .{}); + const tests_root = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + tests_root.addImport("objc-c", objc_c); const tests = b.addTest(.{ .name = "objc-test", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }), + .root_module = tests_root, }); - tests.linkSystemLibrary("objc"); - tests.linkFramework("Foundation"); - tests.linkFramework("AppKit"); // Required by 'tagged pointer' test. + tests.root_module.linkSystemLibrary("objc", .{}); + tests.root_module.linkFramework("Foundation", .{}); + tests.root_module.linkFramework("AppKit", .{}); // Required by 'tagged pointer' test. try addAppleSDK(b, tests.root_module); b.installArtifact(tests); @@ -37,6 +45,72 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&tests_run.step); } +/// Returns a translated Objective-C header module built from the Apple SDK. +/// +/// This patches the single `objc/runtime.h` declaration that currently breaks +/// Zig 0.16 `translate-c`, then translates `objc/runtime.h` and +/// `objc/message.h` into an importable Zig module. Bug report: +/// https://codeberg.org/ziglang/zig/issues/31917 +fn translateCModule( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) !*std.Build.Module { + const sdk_path = try appleSDKPath(b, target); + const include_path = b.pathJoin(&.{ sdk_path, "/usr/include" }); + const runtime_path = b.pathJoin(&.{ include_path, "/objc/runtime.h" }); + const runtime_h = try std.Io.Dir.cwd().readFileAlloc( + b.graph.io, + runtime_path, + b.allocator, + .limited(1024 * 1024), + ); + + // Zig 0.16's translate-c cannot parse Clang block declarators (`^`) in + // objc/runtime.h. Patch just the offending declaration so we still + // translate the real Apple headers rather than maintaining a local shim. + const needle = + \\objc_enumerateClasses(const void * _Nullable image, + \\ const char * _Nullable namePrefix, + \\ Protocol * _Nullable conformingTo, + \\ Class _Nullable subclassing, + \\ void (^ _Nonnull block)(Class _Nonnull aClass, BOOL * _Nonnull stop) + \\ OBJC_NOESCAPE) + ; + // Fail loudly if Apple changes the declaration so we don't silently stop + // patching the one line this workaround depends on. + if (std.mem.indexOf(u8, runtime_h, needle) == null) { + return error.ObjCRuntimeHeaderChanged; + } + + const patched_runtime_h = try std.mem.replaceOwned(u8, b.allocator, runtime_h, needle, + \\objc_enumerateClasses(const void * _Nullable image, + \\ const char * _Nullable namePrefix, + \\ Protocol * _Nullable conformingTo, + \\ Class _Nullable subclassing, + \\ void * _Nonnull block) + ); + + const wf = b.addWriteFiles(); + _ = wf.add("objc/runtime.h", patched_runtime_h); + const import_h = wf.add("objc-import.h", + \\#include + \\#include + \\ + ); + + const c = b.addTranslateC(.{ + .root_source_file = import_h, + .target = target, + .optimize = optimize, + }); + // Search the generated directory first so resolves to the + // patched copy, while every other include still falls through to the SDK. + c.addIncludePath(wf.getDirectory()); + c.addSystemIncludePath(.{ .cwd_relative = include_path }); + return c.createModule(); +} + /// Add the SDK framework, include, and library paths to the given module. /// The module target is used to determine the SDK to use so it must have /// a resolved target. @@ -44,6 +118,13 @@ pub fn build(b: *std.Build) !void { /// The Apple SDK is determined based on the build target and found using /// xcrun, so it requires a valid Xcode installation. pub fn addAppleSDK(b: *std.Build, m: *std.Build.Module) !void { + const path = try appleSDKPath(b, m.resolved_target.?); + m.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ path, "/System/Library/Frameworks" }) }); + m.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ path, "/usr/include" }) }); + m.addLibraryPath(.{ .cwd_relative = b.pathJoin(&.{ path, "/usr/lib" }) }); +} + +fn appleSDKPath(b: *std.Build, target: std.Build.ResolvedTarget) ![]const u8 { // The cache. This always uses b.allocator and never frees memory // (which is idiomatic for a Zig build exe). const Cache = struct { @@ -56,11 +137,10 @@ pub fn addAppleSDK(b: *std.Build, m: *std.Build.Module) !void { var map: std.AutoHashMapUnmanaged(Key, ?[]const u8) = .{}; }; - const target = m.resolved_target.?.result; const gop = try Cache.map.getOrPut(b.allocator, .{ - .arch = target.cpu.arch, - .os = target.os.tag, - .abi = target.abi, + .arch = target.result.cpu.arch, + .os = target.result.os.tag, + .abi = target.result.abi, }); // This executes `xcrun` to get the SDK path. We don't want to execute @@ -68,12 +148,13 @@ pub fn addAppleSDK(b: *std.Build, m: *std.Build.Module) !void { if (!gop.found_existing) { gop.value_ptr.* = std.zig.system.darwin.getSdk( b.allocator, - &m.resolved_target.?.result, + b.graph.io, + &target.result, ); } // The active SDK we want to use - const path = gop.value_ptr.* orelse return switch (target.os.tag) { + return gop.value_ptr.* orelse switch (target.result.os.tag) { // Return a more descriptive error. Before we just returned the // generic error but this was confusing a lot of community members. // It costs us nothing in the build script to return something better. @@ -83,7 +164,4 @@ pub fn addAppleSDK(b: *std.Build, m: *std.Build.Module) !void { .watchos => error.XcodeWatchOSSDKNotFound, else => error.XcodeAppleSDKNotFound, }; - m.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ path, "/System/Library/Frameworks" }) }); - m.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ path, "/usr/include" }) }); - m.addLibraryPath(.{ .cwd_relative = b.pathJoin(&.{ path, "/usr/lib" }) }); } diff --git a/flake.lock b/flake.lock index 4fc4052..ddb3c35 100644 --- a/flake.lock +++ b/flake.lock @@ -47,52 +47,34 @@ "type": "github" } }, - "flake-utils_2": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1759233736, - "narHash": "sha256-xv9wRPGkBfaMil8j2N7Q9LTn9rTiIX3egAGVWnx6Di4=", + "lastModified": 1776390092, + "narHash": "sha256-LmPGFhlBIGktcjde4jhvodiHQ+Anuj+Ya+WU7wv0PFA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6a976061fb5305de938de2571af78755a7ec09ee", + "rev": "76410a99a2c5a2601e05e9d4a7a1ca870edcb616", "type": "github" }, "original": { "owner": "nixos", - "ref": "release-25.05", + "ref": "release-25.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1708161998, - "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", + "lastModified": 1771043024, + "narHash": "sha256-O1XDr7EWbRp+kHrNNgLWgIrB0/US5wvw9K6RERWAj6I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "84d981bae8b5e783b3b548de505b22880559515f", + "rev": "3aadb7ca9eac2891d52a9dec199d9580a6e2bf44", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } @@ -106,6 +88,7 @@ } }, "systems": { + "flake": false, "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -123,15 +106,15 @@ "zig": { "inputs": { "flake-compat": "flake-compat_2", - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "systems": "systems" }, "locked": { - "lastModified": 1759192380, - "narHash": "sha256-0BWJgt4OSzxCESij5oo8WLWrPZ+1qLp8KUQe32QeV4Q=", + "lastModified": 1776396620, + "narHash": "sha256-tb9pe+3rF+Aabn9S+5Y3pLLJzNHnkOsZ7r95XFt9Oss=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "0bcd1401ed43d10f10cbded49624206553e92f57", + "rev": "e8b5f4388e29147015d35635222ba5fcf87d2e3b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bb3a348..64b06b9 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Objective-C runtime bindings for Zig"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/release-25.05"; + nixpkgs.url = "github:nixos/nixpkgs/release-25.11"; flake-utils.url = "github:numtide/flake-utils"; zig.url = "github:mitchellh/zig-overlay"; @@ -35,7 +35,7 @@ in rec { devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ - zigpkgs."0.15.1" + zigpkgs."0.16.0" ]; }; diff --git a/src/block.zig b/src/block.zig index 4df6dca..19591d3 100644 --- a/src/block.zig +++ b/src/block.zig @@ -120,9 +120,9 @@ pub fn Block( _Block_release(@ptrCast(@alignCast(ctx))); } - fn descCopyHelper(src: *anyopaque, dst: *anyopaque) callconv(.c) void { - const real_src: *Context = @ptrCast(@alignCast(src)); + fn descCopyHelper(dst: *anyopaque, src: *anyopaque) callconv(.c) void { const real_dst: *Context = @ptrCast(@alignCast(dst)); + const real_src: *Context = @ptrCast(@alignCast(src)); inline for (captures_info.fields) |field| { if (field.type == objc.c.id) { _Block_object_assign( @@ -150,21 +150,11 @@ pub fn Block( /// the first arg. The first arg is a pointer so from an ABI perspective /// this is always the same and can be safely casted. fn FnType(comptime ContextArg: type) type { - var params: [Args.len + 1]std.builtin.Type.Fn.Param = undefined; - params[0] = .{ .is_generic = false, .is_noalias = false, .type = *const ContextArg }; - for (Args, 1..) |Arg, i| { - params[i] = .{ .is_generic = false, .is_noalias = false, .type = Arg }; - } + var param_types: [Args.len + 1]type = undefined; + param_types[0] = *const ContextArg; + for (Args, 1..) |Arg, i| param_types[i] = Arg; - return @Type(.{ - .@"fn" = .{ - .calling_convention = .c, - .is_generic = false, - .is_var_args = false, - .return_type = Return, - .params = ¶ms, - }, - }); + return @Fn(¶m_types, &@splat(.{}), Return, .{ .@"callconv" = .c }); } }; } @@ -216,24 +206,19 @@ fn BlockContext(comptime Captures: type, comptime InvokeFn: type) type { comptime_float => @compileError("capture should not be a comptime_float, try using @as"), else => {}, } + fields[i] = .{ .name = capture.name, .type = capture.type, .default_value_ptr = null, .is_comptime = false, .alignment = capture.alignment }; + } - fields[i] = .{ - .name = capture.name, - .type = capture.type, - .default_value_ptr = null, - .is_comptime = false, - .alignment = capture.alignment, - }; + var field_names: [fields.len][]const u8 = undefined; + var field_types: [fields.len]type = undefined; + var field_attrs: [fields.len]std.builtin.Type.StructField.Attributes = undefined; + for (fields, 0..) |field, i| { + field_names[i] = field.name; + field_types[i] = field.type; + field_attrs[i] = .{ .@"align" = field.alignment }; } - return @Type(.{ - .@"struct" = .{ - .layout = .@"extern", - .fields = &fields, - .decls = &.{}, - .is_tuple = false, - }, - }); + return @Struct(.@"extern", null, &field_names, &field_types, &field_attrs); } // Pointer to opaque instead of anyopaque: https://github.com/ziglang/zig/issues/18461 @@ -312,18 +297,22 @@ test "Block copy objc id" { const TestBlock = Block(struct { id: objc.c.id, - }, .{}, i32); + }, .{}, objc.c.id); var block = TestBlock.init(.{ .id = obj.value, }, (struct { - fn addFn(block: *const TestBlock.Context) callconv(.c) i32 { - _ = block; - return 0; + fn blockFn(block: *const TestBlock.Context) callconv(.c) objc.c.id { + return block.id; } - }).addFn); + }).blockFn); - // Try copy and release + // Copy the block — this exercises descCopyHelper(dst, src). + // If dst/src are swapped, the copied block's captured id will be garbage + // rather than obj.value, and invoke will return the wrong pointer. const copied = try TestBlock.copy(&block); - TestBlock.release(copied); + defer TestBlock.release(copied); + + const result = TestBlock.invoke(copied, .{}); + try std.testing.expectEqual(obj.value, result); } diff --git a/src/c.zig b/src/c.zig index 46975dc..1933f15 100644 --- a/src/c.zig +++ b/src/c.zig @@ -1,7 +1,4 @@ -pub const c = @cImport({ - @cInclude("objc/runtime.h"); - @cInclude("objc/message.h"); -}); +pub const c = @import("objc-c"); /// On some targets, Objective-C uses `i8` instead of `bool`. /// This helper casts a target value type to `bool`. diff --git a/src/encoding.zig b/src/encoding.zig index 7cfdf1b..271fc7f 100644 --- a/src/encoding.zig +++ b/src/encoding.zig @@ -10,9 +10,7 @@ fn comptimeN(comptime T: type) usize { const encoding = objc.Encoding.init(T); // Figure out how much space we need - var stream: std.io.Writer.Discarding = .init(&.{}); - stream.writer.print("{f}", .{encoding}) catch unreachable; - return stream.count; + return std.fmt.count("{f}", .{encoding}); } } @@ -23,11 +21,10 @@ pub fn comptimeEncode(comptime T: type) [comptimeN(T):0]u8 { // Build our final signature var buf: [comptimeN(T) + 1]u8 = undefined; - var fbs: std.io.Writer = .fixed(buf[0 .. buf.len - 1]); - fbs.print("{f}", .{encoding}) catch unreachable; - buf[buf.len - 1] = 0; + const result = std.fmt.bufPrint(buf[0 .. buf.len - 1], "{f}", .{encoding}) catch unreachable; + buf[result.len] = 0; - return buf[0 .. buf.len - 1 :0].*; + return buf[0..result.len :0].*; } } @@ -107,7 +104,7 @@ pub const Encoding = union(enum) { pub fn format( comptime self: Encoding, - writer: *std.io.Writer, + writer: anytype, ) !void { switch (self) { .char => try writer.writeAll("c"), diff --git a/src/msg_send.zig b/src/msg_send.zig index e9678da..44b069b 100644 --- a/src/msg_send.zig +++ b/src/msg_send.zig @@ -36,8 +36,11 @@ pub fn MsgSend(comptime T: type) type { // Build our function type and call it const Fn = MsgSendFn(RealReturn, @TypeOf(target.value), @TypeOf(args)); const msg_send_fn = comptime msgSendPtr(RealReturn, false); - const msg_send_ptr: *const Fn = @ptrCast(msg_send_fn); - const result = @call(.auto, msg_send_ptr, .{ target.value, sel.value } ++ args); + const msg_send_ptr: *const Fn = @ptrCast(@alignCast(msg_send_fn)); + + // Unwrap any Object types in args to their underlying c.id + const unwrapped_args = buildUnwrappedArgs(args); + const result = @call(.auto, msg_send_ptr, .{ target.value, sel.value } ++ unwrapped_args); if (!is_object) return result; return .{ .value = result }; @@ -62,7 +65,7 @@ pub fn MsgSend(comptime T: type) type { const Fn = MsgSendFn(RealReturn, *c.objc_super, @TypeOf(args)); const msg_send_fn = comptime msgSendPtr(RealReturn, true); - const msg_send_ptr: *const Fn = @ptrCast(msg_send_fn); + const msg_send_ptr: *const Fn = @ptrCast(@alignCast(msg_send_fn)); var super: c.objc_super = if (comptime @hasField(c.objc_super, "super_class")) .{ @@ -74,7 +77,10 @@ pub fn MsgSend(comptime T: type) type { .receiver = target.value, .class = superclass.value, }; - const result = @call(.auto, msg_send_ptr, .{ &super, sel.value } ++ args); + + // Unwrap any Object types in args to their underlying c.id + const unwrapped_args = buildUnwrappedArgs(args); + const result = @call(.auto, msg_send_ptr, .{ &super, sel.value } ++ unwrapped_args); if (!is_object) return result; return .{ .value = result }; @@ -188,36 +194,76 @@ fn MsgSendFn( // are an "id" so we just make sure the sizes match for ABI reasons. assert(@sizeOf(Target) == @sizeOf(c.id)); - // Build up our argument types. - const Fn = std.builtin.Type.Fn; - const params: []Fn.Param = params: { - var acc: [argsInfo.fields.len + 2]Fn.Param = undefined; - - // First argument is always the target and selector. - acc[0] = .{ .type = Target, .is_generic = false, .is_noalias = false }; - acc[1] = .{ .type = c.SEL, .is_generic = false, .is_noalias = false }; - - // Remaining arguments depend on the args given, in the order given - for (argsInfo.fields, 0..) |field, i| { - acc[i + 2] = .{ - .type = field.type, - .is_generic = false, - .is_noalias = false, - }; - } + // Build up our argument types for @Fn + var param_types: [argsInfo.fields.len + 2]type = undefined; + param_types[0] = Target; + param_types[1] = c.SEL; + for (argsInfo.fields, 0..) |field, i| param_types[i + 2] = unwrapType(field.type); - break :params &acc; - }; + return @Fn(¶m_types, &@splat(.{}), Return, .{ .@"callconv" = .c }); +} + +fn UnwrappedArgs(comptime Args: type) type { + const fields = @typeInfo(Args).@"struct".fields; + var types: [fields.len]type = undefined; + for (fields, 0..) |field, i| types[i] = unwrapType(field.type); + return @Tuple(&types); +} - return @Type(.{ - .@"fn" = .{ - .calling_convention = .c, - .is_generic = false, - .is_var_args = false, - .return_type = Return, - .params = params, +/// Maps objc wrapper types to their underlying C types for use in @Fn signatures, +/// and validates that all other types are C-ABI compatible. +fn unwrapType(comptime T: type) type { + // Unwrap our objc.Object type + if (T == objc.Object) return c.id; + + // Unwrap any other objc wrapper (Class, Sel, etc.) — identified by having + // a single 'value' field of pointer size. Return the actual field type + // rather than c.id, since Class and Sel have distinct pointer types. + if (@typeInfo(T) == .@"struct") { + const info = @typeInfo(T).@"struct"; + for (info.fields) |field| { + if (std.mem.eql(u8, field.name, "value") and @sizeOf(field.type) == @sizeOf(c.id)) { + return field.type; + } + } + } + + // Validate that the remaining type is safe to pass over the C ABI. + // Previously (pre-0.16), passing a non-C-compatible type like []const u8 + // would silently compile but segfault at runtime via objc_msgSend. + // These checks turn that into a compile error. + switch (@typeInfo(T)) { + .int, .float, .bool, .void => {}, + .@"enum" => {}, + .pointer => {}, + .optional => |opt| { + if (@typeInfo(opt.child) != .pointer) + @compileError("msgSend: " ++ @typeName(T) ++ " — optional must wrap a pointer"); + }, + .@"struct" => |s| { + if (s.layout != .@"extern" and s.layout != .@"packed") + @compileError("msgSend: " ++ @typeName(T) ++ " — struct must be extern or packed"); }, - }); + .@"union" => |u| { + if (u.layout != .@"extern") + @compileError("msgSend: " ++ @typeName(T) ++ " — union must be extern"); + }, + else => @compileError("msgSend: " ++ @typeName(T) ++ " — not C-ABI compatible"), + } + + return T; +} + +inline fn buildUnwrappedArgs(args: anytype) UnwrappedArgs(@TypeOf(args)) { + const fields = @typeInfo(@TypeOf(args)).@"struct".fields; + var result: UnwrappedArgs(@TypeOf(args)) = undefined; + inline for (fields, 0..) |_, i| { + result[i] = if (unwrapType(@TypeOf(args[i])) != @TypeOf(args[i])) + args[i].value + else + args[i]; + } + return result; } test { diff --git a/src/object.zig b/src/object.zig index aaa762d..d7d59e2 100644 --- a/src/object.zig +++ b/src/object.zig @@ -44,7 +44,7 @@ pub const Object = struct { /// Returns the class name of a given object. pub fn getClassName(self: Object) [:0]const u8 { - return std.mem.sliceTo(c.object_getClassName(self.value), 0); + return std.mem.span(c.object_getClassName(self.value)); } /// Set a property. This is a helper around getProperty and is @@ -218,3 +218,22 @@ test "tagged pointer" { try testing.expect(!std.mem.isAligned(obj_ptr, @alignOf(usize))); try testing.expect(std.meta.eql(obj, Object.fromId(obj.value))); } + +test "msgSend with objc wrapper args" { + // Verifies that objc.Object, objc.Class, and objc.Sel are correctly + // unwrapped to c.id / c.SEL when passed as msgSend arguments. + const testing = std.testing; + const NSObject = objc.getClass("NSObject").?; + + const obj = NSObject.msgSend(objc.Object, "alloc", .{}); + _ = obj.msgSend(objc.Object, "init", .{}); + defer obj.msgSend(void, "dealloc", .{}); + + // Pass objc.Class as an argument: -[NSObject isKindOfClass:] + const is_nsobject = obj.msgSend(bool, "isKindOfClass:", .{NSObject}); + try testing.expect(is_nsobject); + + // Pass objc.Sel as an argument: -[NSObject respondsToSelector:] + const responds = obj.msgSend(bool, "respondsToSelector:", .{objc.Sel.registerName("init")}); + try testing.expect(responds); +} diff --git a/src/property.zig b/src/property.zig index e042306..a99c48e 100644 --- a/src/property.zig +++ b/src/property.zig @@ -7,15 +7,13 @@ pub const Property = extern struct { /// Returns the name of a property. pub fn getName(self: Property) [:0]const u8 { - return std.mem.sliceTo(c.property_getName(self.value), 0); + return std.mem.span(c.property_getName(self.value)); } /// Returns the value of a property attribute given the attribute name. pub fn copyAttributeValue(self: Property, attr: [:0]const u8) ?[:0]u8 { - return std.mem.sliceTo( - c.property_copyAttributeValue(self.value, attr.ptr) orelse return null, - 0, - ); + const ptr = c.property_copyAttributeValue(self.value, attr.ptr) orelse return null; + return std.mem.span(ptr); } comptime { diff --git a/src/protocol.zig b/src/protocol.zig index 24b64b0..4627bc6 100644 --- a/src/protocol.zig +++ b/src/protocol.zig @@ -17,7 +17,7 @@ pub const Protocol = extern struct { } pub fn getName(self: Protocol) [:0]const u8 { - return std.mem.sliceTo(c.protocol_getName(self.value), 0); + return std.mem.span(c.protocol_getName(self.value)); } pub fn getProperty( diff --git a/src/sel.zig b/src/sel.zig index 8036961..850981d 100644 --- a/src/sel.zig +++ b/src/sel.zig @@ -19,7 +19,7 @@ pub const Sel = struct { /// Returns the name of the method specified by a given selector. pub fn getName(self: Sel) [:0]const u8 { - return std.mem.sliceTo(c.sel_getName(self.value), 0); + return std.mem.span(c.sel_getName(self.value)); } };