Does cImport really work at the file level?

I’m curious how self-contained @cImport blocks really are.

If I have a file:

const c = @cImport({
    @cInclude("some_header.h");
});

pub const FancyType = c.fancy_type;

const PrivateType = opaque{};

I know that when I import this file into another, FancyType will be visible but PrivateType won’t be. It only exists within this one file, so the Zig compiler won’t even process it while dealing with other files (right?).

Likewise, the cImport is not public. However, I’m not sure if it gets the same treatment as Zig variables. At what point do the C headers get converted to Zig?

Whenever I get build errors regarding the imported C headers, the compiler is always referencing a cimport.zig file in the cache. This makes it seem that the cImport statements are actually separate from the file that contains them. Which makes me wonder if the compiler treats them special, or if after conversion they are treated like any other zig source file.

In other words, if I were to manually convert some C headers into Zig code and write them into a file (i.e. “some_headers.zig”), and then import them normally, would the end result be identical to what cImport does?

I guess I’m trying to understand the limits of how they can get used, and why external code sometimes complains about missing C headers when they shouldn’t have even known they existed.

So, there is the Zig ABI (Application Binary Interface) and the C ABI. Those ABIs specify, for example, how to call functions or how structs are laid out. Zig’s struct type, for example, is optimised by the compiler, so if you want know the layout of a struct exactly, it’s better to use the C ABI struct. This C ABI struct is also known extern struct.
Then we have functions. They have so-called calling conventions. The name is already a hint: a calling convention specifies how arguments are delivered. In Zig, you can set a calling convention with callconv. Below an example function.

fn someFunction(someArgument: u32) callconv(.C) u32 {
    return someArgument;
}

Note that if you want to expose a Zig function to C code, you can use export fn which directly sets the calling convention to C (so you don’t have to do that manually).

But in header files, we often see only function declarations, not function implementations (or whatever you want to call the functions in the .c, not .h files).
If you want to define a function, you can do it using extern fn:

extern "c" fn atan2(a: f64, b: f64) f64;

This was an example from the Zig Language Reference. The “c” means this symbol comes from libc.

And because you can do (almost?) any C thing in Zig, zig just translate-cs the files from @cImport. Macros are just expanded, as far as I know.

2 Likes