ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
33.7k stars 2.47k forks source link

resurrect emit-h #9698

Open McSinyx opened 3 years ago

McSinyx commented 3 years ago

I am converting a codebase from C++ to Zig. So far, I switched the entry point to Zig and recreate the C++ objects in Zig. The rest in the middle are still (extern "C") C++, i.e. we have a Zig sandwich here.

In order for the C++ code to use the Zig struct, there must be a header file and unfortunately it is also translated to Zig. Thus, this shared module is recognized both as originally declared and .cimport[…].struct_[…] and the (stage 1) compiler yields the error: expected type […], found […]. I made a minimal reproducer here hoping it'll help debugging.

I also noticed that stage 1 compiler has disabled C header emission (GH-6753), was this one of the reasons?

Vexu commented 3 years ago

All struct types are unique, you cannot mix the automatically translated struct types with handwritten definitions.

McSinyx commented 3 years ago

I suppose the right way to do it here will be to @.***the struct from the C header? What should happen when the header is emitted byzig build`? What I wrote by hand was identical to what -femit-h gives, except for the C++ compatibility.

McSinyx commented 3 years ago

Wow using emails with GitHub is awful, this is the original message:

I suppose the right way to do it here will be to @cImport the struct from the C header? What should happen when the header is emitted by zig build? What I wrote by hand was identical to what -femit-h gives, except for the C++ compatibility.

Vexu commented 3 years ago

I suppose the right way to do it here will be to @cImport the struct from the C header?

Either that or add casts where needed.

What should happen when the header is emitted by zig build? What I wrote by hand was identical to what -femit-h gives, except for the C++ compatibility.

Unless there is a dependency loop, this should be pretty easy to do by chaining a EmitHStep (once such exists), to a TranslateCStep and then using that as a package.

McSinyx commented 3 years ago

Wouldn't the translated struct from emitted header still be different from the original one? I don't see how that will make the useFoo(makeFoo()) call in Zig legal.

babelchips commented 3 years ago

I agree. I'm inclined to think we'll have different structs in play even when headers can be emitted.

For example when writing a combination of Zig and C code where you wish to share a Zig defined structure:

  1. Emit a new Zig packed struct (assuming the emit feature exists)
  2. Include the header in your C code
  3. From C, call into an exported Zig function, passing the struct (or a reference to it) as an argument
  4. Surely the compilation will fail with error: expected type […], found […] ... ?

You couldn't even use cImport with cInclude of the exported header back in Zig because the header file would not exist initially (or be out of date). Not that you would want to use imported versions of your own packed struct in the Zig code.

So, outside of using void* and casting either side, what is the ultimate solution here?

SpexGuy commented 3 years ago

You should define your structs in only one place. If you define them on the C side, you can cImport that in exactly one place, and everything will work. If you define them on the Zig side, you will also need to write your own extern function definitions, or post-process the generated .zig file from translate-c to fix up references.

McSinyx commented 3 years ago

You should define your structs in only one place.

Agreed, but this doesn't account for emitted structs. The only reason the struct was hand-written in the example was because emit-h has been disabled for stage 1 since 0.7. I think that seamless interoperation with C is one of the goals of Zig, and getting structures defined in Zig to work in C should be a supported use case, as it's crucial for writing C ABI libraries in Zig.

andrewrk commented 2 years ago

getting structures defined in Zig to work in C should be a supported use case, as it's crucial for writing C ABI libraries in Zig.

Completely agreed. The plan is to resurrect the emit-h feature in the self-hosted compiler, which is under active development.

Renha commented 7 months ago

The "Add a Zig compilation unit to C/C++ projects" point in "⚡ Maintain it with Zig" section of main advertised language features should be removed until the header generation is back in a working state

mlugg commented 7 months ago

@Renha, why? That feature works entirely as described - header generation is a completely separate issue.

Renha commented 7 months ago

So you could add Zig unit to the project, but not use it, and that was the intention?

abhinav commented 7 months ago

@Renha lack of emit-h doesn't make Zig compilation units unusable. Right now, it's best to hand-write a C header file for your Zig library if you're exposing a C ABI. Fixing emit-h would generate the header file for you automatically. Yeah, that would be more convenient but it's not unusable.

Renha commented 7 months ago

Thank you, sorry if my tone sounded too harsh, I hope things will improve soon so that I would be able to use Zig in my project.

mcharytoniuk commented 5 months ago

As a workaround, you can usually get good results by asking an LLM to generate C headers from the Zig code. They are good at converting between formats.

saltzm commented 5 months ago

@abhinav Do you know of any public examples of handwritten headers for zig libraries that I could look at?

abhinav commented 5 months ago

@saltzm Yeah, sure! Off the top of my head:

If I had to guess, most projects using Zig 0.11 or newer, and exposing a C API would be hand-writing them right now.

saltzm commented 5 months ago

Thanks for the quick response! I'm looking at the first one. So basically it looks like the functions in the header you linked correspond to the exported functions from c_api.zig, and then in the header you linked they're defining some opaque structs where they manually determine the size of each struct in order to allow the user to statically allocate these structs.

The latter bit is something I would want to do since I don't want my struct to be required to be heap allocated, but it definitely seems a bit difficult/convoluted.

I can think of a workaround that would be providing an allocator to a myobj_init function, and doing something like the FixedBufferAllocator pattern to allow people to allocate the object on the stack - they'd just still need to know how large to make the object.

Feel like I'm getting off topic here though, I think I get the gist now so thank you! Currently discussing this in the Zig Discord channel on zig-help by the way.

EDIT: Ah, I see that the Tigerbeetle people are just redefining the struct in C to avoid the opaque struct stuff the libxev person is doing.

Des-Nerger commented 3 months ago

As of 0.13.0-dev.344+b2588de6c, a C header is generable through $ zig test -femit-h src/root.zig, but what about making the same through build.zig? I tried a solution suggested on Discord:

const install_lib = b.addInstallArtifact(lib, .{ .h_dir = .{ .override = .header } });
install_lib.emitted_h = lib.getEmittedH();
b.getInstallStep().dependOn(&install_lib.step);

, but got this: EmittedH_0.13.0-dev.344+b2588de6c.log. Is it a bug?