Thread overview
ImportC: On the issue of using a type from two different C files
Aug 27, 2022
TheGag96
Aug 27, 2022
Mike Parker
Aug 27, 2022
Paul Backus
Aug 27, 2022
Dave P.
Aug 27, 2022
Adam D Ruppe
Aug 27, 2022
TheGag96
August 27, 2022

I've been trying out ImportC with libgit2 lately, and it's honestly a bit like magic!! Kudos to Walter for his work so far - this has been really cool. However, you may have seen Adam's blog post about the issue that D's module namespaces create, where a type imported from two different .c files aren't compatible with each other. There's a Bugzilla issue about it too, which Walter initially closed as WONTFIX because it seemed like it needed too difficult a fix.

Zig advertises a similarly "magic" C importing feature. I made an example trying a similar scenario to what Adam described, and it seems we're actually in "good company":

<proj path>/src/main.zig:11:16: error: expected type '.media.<username>.4ea9c2fa-455c-480d-adbd-b533afd47647.home.<username>.Coding.Zig.zigwithc.zig-cache.o.8cf07da78935aad995196c635ddb6192.cimport.struct_tm', found '.media.<username>.4ea9c2fa-455c-480d-adbd-b533afd47647.home.<username>.Coding.Zig.zigwithc.zig-cache.o.46100012acc16f9e9848385e1fd2122e.cimport.struct_tm'
    clibB.doIt(thing);
               ^~~~~
<proj path>/zig-cache/o/46100012acc16f9e9848385e1fd2122e/cimport.zig:121:30: note: struct declared here
pub const struct_tm = extern struct {
                      ~~~~~~~^~~~~~
<proj path>/zig-cache/o/8cf07da78935aad995196c635ddb6192/cimport.zig:121:30: note: struct declared here
pub const struct_tm = extern struct {
                      ~~~~~~~^~~~~~

I'd like to know how they plan to solve this issue themselves... Maybe we can help each other out in this regard.

I asked Adam about it on the Discord, and his suggestion was:

>

but there is a pretty easy solution to this in any case: make everything from importC be an alias back into an implicitly-created global namespace

What do you think? This at least seems like a sound idea.

August 27, 2022

On Saturday, 27 August 2022 at 02:34:39 UTC, TheGag96 wrote:

>

What do you think?

This has been discussed a couple of times in our Foundation meetings. The most recent was in May. See Walter's section in the sumamry:

https://forum.dlang.org/thread/uhgndrcnekedjqtarnwl@forum.dlang.org

Iain suggested the following:

>

ImportC symbols, rather than going into a module space, should go into a global ImportC module. Each imported C file or header file is then appended to that module.

Martin disagreed, and proposed an alternative:

>

Martin said that one problem with that approach is that a D module would have access to symbols it didn't import. He suggested the ideal solution would be for each header to have its own module that doesn't change from one invocation of the compiler to the next.

August 27, 2022

On Saturday, 27 August 2022 at 03:07:25 UTC, Mike Parker wrote:

>

Martin disagreed, and proposed an alternative:

>

Martin said that one problem with that approach is that a D module would have access to symbols it didn't import. He suggested the ideal solution would be for each header to have its own module that doesn't change from one invocation of the compiler to the next.

This pretty much requires DMD to incorporate a C preprocessor, right? Since that's the only way it can reliably determine which headers are included.

August 27, 2022

On Saturday, 27 August 2022 at 03:07:25 UTC, Mike Parker wrote:

>

On Saturday, 27 August 2022 at 02:34:39 UTC, TheGag96 wrote:

>

What do you think?

This has been discussed a couple of times in our Foundation meetings. The most recent was in May. See Walter's section in the sumamry:

https://forum.dlang.org/thread/uhgndrcnekedjqtarnwl@forum.dlang.org

Iain suggested the following:

>

ImportC symbols, rather than going into a module space, should go into a global ImportC module. Each imported C file or header file is then appended to that module.

Martin disagreed, and proposed an alternative:

>

Martin said that one problem with that approach is that a D module would have access to symbols it didn't import. He suggested the ideal solution would be for each header to have its own module that doesn't change from one invocation of the compiler to the next.

IMO, the best solution is to implement some variation of C23’s rules for tag compatibility where identical types with identical tags are treated as equivalent. Importing into a global C namespace means you can’t use two C headers with conflicting definitions of a type. Ideally, using C from D should be better than using C from C and allow you to resolve this situation via the module system while still allowing you to mix compatible types between C modules.

August 27, 2022

I asked the Zig Discord about their plans. Andrew Kelley himself actually responded:

>

projects should try to have only 1 c import for all C interfacing

So they're satisfied, at least for the moment, with how things work now. Hopefully D can solve it the most satisfyingly. ImportC is a great feature for D, I think.

August 27, 2022
On Saturday, 27 August 2022 at 03:07:25 UTC, Mike Parker wrote:
> Martin disagreed, and proposed an alternative:
>
>> Martin said that one problem with that approach is that a D module would have access to symbols it didn't import. He suggested the ideal solution would be for each header to have its own module that doesn't change from one invocation of the compiler to the next.

This is why I said "be an alias" in the chat thread. The `import c` module essentially selectively imports just the things actually declared from the magic module, giving the same behavior, but since the canonical name+definition is elsewhere and merged in there, the aliases won't cause type conflicts.

The implementation might be easier said than done, since you'd have a pseudo-module being mutated through the import process. I expect a high probability of forward reference bugs cropping up. But the concept is something we can test by hand. Taking the same example from my blog that fails with importC on dmd master an doing instead:

---
module __magic_importC;

// representing all the C definitions imported
struct FILE;
extern(C) int printf(const char*, ...);
extern(C) int fclose(FILE*);
extern(C) void saySomethingToAFile(FILE*);
extern(C) FILE* openAFile();
---

then

---
module b;

// it does a public selective import
public import magic : FILE, printf, fclose, openAFile;
---

and

---
module b2;

// again public selective import
public import magic : FILE, printf, fclose, saySomethingToAFile;
---


And now the test program compiles successfully:

---
import b;
import b2;

void main() {
        auto fp = openAFile();
        scope(exit) fclose(fp);
        saySomethingToAFile(fp);

        printf("Hello\n");
}
---


and if you remove an import, you correctly get:

d.d(7): Error: undefined identifier `saySomethingToAFile`


thanks to the selective import mechanism.


This scheme is compatible with both C and D declaration rules, doesn't have a big namespace surprise (the compiler should also just forbid importing the magic internal implementation module so people don't try to poke that directly), and.... might be doable. Again, possibility of bugs with the extraordinary magic module being mutated through the process, but since that's all inside dmd  thanks to the selective import hiding anything you can't see in the right order anyway and the mutations are strictly additive we ought to be able to cover it up and make it work.