# A mean to attach methods to C structs

**URL:** <https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892>\
**Category:** Brainstorming\
**Tags:** language, c\
**Created:** [October 8, 2026, 10:16pm UTC](https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892 "2026-10-08T22:16:07Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [October 8, 2026, 10:16pm UTC](https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892/1 "2026-10-08T22:16:07Z")

</div>

I’m currently in middle of refactoring the API code that I used for my PHP extension. The initial version was very low effort. For text strings I have this in php.zig:

```zig
pub const String = c.zend_string;

```

And associated functions:

```zig
pub fn getStringContent(str: *const String) [:0]const u8 {
    const s: [*]const u8 = @ptrCast(&str.*.val[0]);
    const len = str.*.len;
    return @ptrCast(s[0..len]);
}

pub fn compareStrings(s1: *const String, s2: *const String) bool {
    const sc1 = getStringContent(s1);
    const sc2 = getStringContent(s2);
    return std.mem.eql(u8, sc1, sc2);
}

pub fn parseBool(s: *String) bool {
    return c.zend_ini_parse_bool(s);
}

pub fn parseLong(s: *String) Long {
    return c.zend_atol(&s.val[0], s.len);
}

```

Just a straight mapping to the C API basically. Extremely low-effort.

When the refactoring, I want to make the API more idiomatic Zig. More OOPish. To that end, I decided to wrap the PHP/Zend string type in a `struct` and attach various helper methods to it:

```zig
pub const String = struct {
    impl: c.zend_string,    

    pub fn slice(self: *const @This()) [:0]const u8 {
        const s: [*]const u8 = @ptrCast(&self.impl.val[0]);
        const len = self.impl.len;
        return @ptrCast(s[0..len]);
    }

    pub fn length(self: *const @This()) usize {
        return self.impl.len;
    }
    
    // etc...
}

```

At the binary level, `String` contains exactly the data as `c.zend_string`. That allows me to freely between `*c.zend_string` and `*String`. Every times where this occurs though, it requires the use of `@ptrCast()`. That includes situations involving pointers to functions accepting strings as arguments.

It would be nice if the compiler would recognize that `String` and `c.zend_string` are the same thing and convert pointers automatically.

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [October 8, 2026, 10:25pm UTC](https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892/2 "2026-10-08T22:25:46Z")

</div>

If the ABI of `c.zend_string` is part of the public API of the library you are porting, you could work as follows:

```zig
pub const ZendString = extern struct { // extern is required
    // fields here
    pub fn length(str: *ZendString) {
        return zend_length(str);
    }
    extern "c" fn zend_length(str: ?*ZendString); //optional bc C
};

```

This will work just fine, but it does require you to take some ownership of the `translate-c` output.

If the ABI of `c.zend_tring` is _not_ part of the public API of the library, then usually the library itself has functions in charge of creating and destroying the struct and users should work only with handles to it. In this case, you could still work similarly to the above, but instead you’d write something like

```zig
pub const ZendString = opaque { // opaque types must be passed by pointer
    // fields here
    pub fn length(str: *ZendString) {
        return zend_length(str);
    }
    extern "c" fn zend_length(str: ?*ZendString); //optional bc C
};

```

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [October 8, 2026, 11:45pm UTC](https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892/3 "2026-10-08T23:45:50Z")

</div>

The number of functions is quite large. And as we know, it’s not possible to attach new decls to a comptime-defined struct. In theory though, I could use comptime fields. It really shouldn’t matter whether I’m performing `.[function name]` on a namespace or a 0-byte struct.

An ability to attach methods to structs generator by translate-c would be a cleaner solution.

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [October 9, 2026, 1:14am UTC](https://ziggit.dev/t/a-mean-to-attach-methods-to-c-structs/17892/4 "2026-10-09T01:14:45Z")

</div>

I don’t know if you didn’t understand what I was suggesting or what, but if you’re interested in going on beyond `translate-c`, you might appreciate having `vulkan-zig` as an example to follow. i’m quite sure you won’t have more functions than they do 🙂
