Cast C struct to the same C struct but from another zig module

I don’t know what tool I really need but I would like (to shoot my leg again) and just have force cast

So I have two C libraries (both of which I independently use) and one of them exposes API which takes as parameter type from another one

//lib1.h
struct lib1_struct {...}
//lib2.h
#include <lib1.h>
void lib2_func(lib1_struct);

I have two modules added via translate-c so in the end I have something like this in my zig file:

const lib1 = @import("lib1");
const lib2 = @import("lib2");

const tmp = lib1.lib1_struct {...};
lib2.lib2_func(tmp); // Error expected ...lib2.lib1_struct but got lib1.lib1_struct

I essentially know they are the same thing but can’t tell that to compiler

it would be safe to ptr/bit cast them if they are c/extern structs with the same layout.

But ideally you shouldn’t have duplicate translated headers in the first place.

Why not have just one translate-c module?

// c.h
#include <lib1.h>
#include <lib2.h>
const c = @import("c");

That way there’d only be one type.

2 Likes
  1. Unfortunately:
//lib1.zig
#define DISABLE_THING
#include <lib1.h>
//lib2.zig
// #define DISABLE_THING will disable thing in lib1 but also
// will disable thing in lib2 which I need(because it looks on what is inside lib1)
// And yes it is actually safe... Just a bad design of two libs
#include <lib1.h>
#include <lib2.h>
  1. I don’t need lib2 in all the places but it is just an autocomplete issue

@ptrCast does work but not @bitCast

Could you not do

#include <lib2.h>
#define DISABLE_THING
#include <lib1.h>

or

#define DISABLE_THING
#include <lib1.h>
#undef DISABLE_THING
#include <lib2.h>

If neither of those works then a @bitCast() or @ptrCast() are probably the best option.

Alternatively though, you can also try to to write your own bindings and go around translate-c altogether.

extern "lib1" lib1_foo(arg1: c_int, arg2: lib1_SomeStruct) void;
extern "lib2" lib2_bar() lib1_SomeStruct;

const lib1_SomeStruct = extern struct {
    field1: c_int,
    field2: [*]c_char,
};
1 Like

Well, I would like to do it but lib2 actually looks to a thing which is conditionally defined inside lib1 so it is more something like:


//lib1.h
#ifndef DISABLE_THING
    #define THING_AVAILABLE
    void thing() {...}
#endif

//lib2.h
#ifdef THING_AVAILABLE
    void some_func() { // The only thing from lib2 I need at all :)
         thing();
    }
#endif

And so I need both versions of lib1 both with disabled and enabled thing. So I yeah, inherited from C dependency hell…

I’m becoming awfully curious what two libraries it is that you are using that do things line that.

edit: And even MORE so how it is possible that using those libraries like that is safe!

Well

It is a noname extension to a noname “engine” on SDL2 written in very strange state of mind. Like if there is no THING feature in original lib why to even define function used only with a THING? Which is great idea but awfully implemented…

That func in lib2 generally gives you almost static list of requirements (which you than need to check). Nothing special and it doesn’t depend much on anything except on data returned from lib1 (which in turn also very self-containing). So it would be safe to use the func from lib1 even with disabled lib1 (except it is all just not defined than…)

As said in one of previous messages @bitCast doesn’t work even though they have identical layout but well @ptrCast works so hacky thing is possible:

  1. get pointer
  2. cast pointer
  3. de-reference pointer
var tmp: lib1.struct = {...}
@as(*lib2.struct, @ptrCast(&tmp)).*

Thanks all for helping!

Slightly related, but I wished that more languages would offer optional structural typing, e.g. an annotation on struct declarations that makes two independently defined structs with different names but with the same ‘interior layout’ assignable to each other without an explicit cast.

1 Like