www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - extern(C) symbol conflicts

reply Steven Schveighoffer <schveiguy yahoo.com> writes:
An interesting thing I learned while reading through some bug reports [1]:

cfile.c
#include <stdio.h>
void cfunction() {printf("hello\n");}

file1.d
module file1;
extern(C) void cfunction();

file2.d
module file2;
extern(C) void cfunction();

main.d
version(test1)
{
    import file1;
}
version(test2)
{
    import file2;
}

void main()
{
    cfunction();
}

cc -c cfile.c
dmd -version=test1 main.d file1.d file2.d cfile.o
./main
hello

dmd -version=test2 main.d file1.d file2.d cfile.o
./main
hello

All fine and good. Now:

dmd -version=test1 -version=test2 main.d file1.d file2.d cfile.o

main.d(12): Error: file2.cfunction at file2.d(2) conflicts with 
file1.cfunction at file1.d(2)

What gives here? cfunction is not part of any module, it's extern(C). In 
fact, both equate to the same symbol (as shown by the different ways we 
can import with only one implementation). But D considers them 
different. Why?

I would have expected that any time you declare (but don't define) an 
extern(C) symbol, it's just like a prototype -- if it's already declared 
no big deal. But it shouldn't be module-based.

Is there a good reason why we shouldn't allow the duplicate declaration 
in multiple modules? I understand for D symbols -- those are actually 
different symbols.

This is actually a problem someone may encounter quite a bit -- 2 
different libraries or even modules from the same library (see 
referenced bug) may create their own bindings to C functions. I would 
say, let's just let the linker figure it out, no?

-Steve

[1] https://issues.dlang.org/show_bug.cgi?id=7729
Jan 26 2015
next sibling parent "Kagamin" <spam here.lot> writes:
Does this work?

void main()
{
    version(test1)test1.cfunction();
    else test2.cfunction();
}

All symbols belong to modules.
Jan 27 2015
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/26/2015 11:06 AM, Steven Schveighoffer wrote:
 Is there a good reason why we shouldn't allow the duplicate declaration in
 multiple modules? I understand for D symbols -- those are actually different
 symbols.
D's interface to C and C++ does not adopt C and C++ semantics, in particular, it does not adopt C and C++ name lookup rules, function overloading rules, template instantiation rules, etc. This is on purpose to reduce the complexity of the language. As in C and C++, it is up to the D programmer to follow the One Definition Rule when interfacing with those languages. Declaring the same function in multiple modules is a bad idea. (Yes, I know, we had to bend that a bit to make C++ namespaces work, but we did as little as possible.)
Jan 27 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/27/15 5:57 AM, Walter Bright wrote:
 On 1/26/2015 11:06 AM, Steven Schveighoffer wrote:
 Is there a good reason why we shouldn't allow the duplicate
 declaration in
 multiple modules? I understand for D symbols -- those are actually
 different
 symbols.
D's interface to C and C++ does not adopt C and C++ semantics, in particular, it does not adopt C and C++ name lookup rules, function overloading rules, template instantiation rules, etc. This is on purpose to reduce the complexity of the language.
I never said anything about C++. I don't see how that is relevant. C has no overloading rules or template instantiation rules.
 As in C and C++, it is up to the D programmer to follow the One
 Definition Rule when interfacing with those languages. Declaring the
 same function in multiple modules is a bad idea.
The problem I see is that C bindings are easy to make. If I need to reference I function, I just declare it. If some other library writer needs the same function, he declares it. But now, if anyone wants to use both libraries, you have to pick one or the other, even though they are the same. I understand the idea behind keeping the lookup rules consistent. But C lookup rules ARE simple. I would say if two extern(C) declarations are identical (i.e. same parameter types, same attributes), they don't conflict. What does this break? -Steve
Jan 27 2015
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-01-27 19:13, Steven Schveighoffer wrote:

 I would say if two extern(C) declarations are identical (i.e. same
 parameter types, same attributes), they don't conflict. What does this
 break?
It it's extern(C), shouldn't just the name matter? -- /Jacob Carlborg
Jan 27 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/27/15 3:11 PM, Jacob Carlborg wrote:
 On 2015-01-27 19:13, Steven Schveighoffer wrote:

 I would say if two extern(C) declarations are identical (i.e. same
 parameter types, same attributes), they don't conflict. What does this
 break?
It it's extern(C), shouldn't just the name matter?
For instance, if one library tags it as pure, but another does not. I think an error in that case is warranted. -Steve
Jan 27 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-01-27 21:31, Steven Schveighoffer wrote:

 For instance, if one library tags it as pure, but another does not. I
 think an error in that case is warranted.
Yeah. Do the compiler need to look at the parameters as well? Even if you put const or immutable, it won't make difference on the C side. But it will probably be confusing if one is declared const and another is not. -- /Jacob Carlborg
Jan 27 2015
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/27/2015 11:22 PM, Jacob Carlborg wrote:
 On 2015-01-27 21:31, Steven Schveighoffer wrote:

 For instance, if one library tags it as pure, but another does not. I
 think an error in that case is warranted.
Yeah. Do the compiler need to look at the parameters as well? Even if you put const or immutable, it won't make difference on the C side. But it will probably be confusing if one is declared const and another is not.
C does not have name mangling, so: extern(C) void foo(int); extern(C) void foo(char); will mangle to the same name, although D will regard them as different symbols. C++ treats extern"C" names the same way.
Jan 27 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/28/15 2:49 AM, Walter Bright wrote:
 On 1/27/2015 11:22 PM, Jacob Carlborg wrote:
 On 2015-01-27 21:31, Steven Schveighoffer wrote:

 For instance, if one library tags it as pure, but another does not. I
 think an error in that case is warranted.
Yeah. Do the compiler need to look at the parameters as well? Even if you put const or immutable, it won't make difference on the C side. But it will probably be confusing if one is declared const and another is not.
C does not have name mangling, so: extern(C) void foo(int); extern(C) void foo(char); will mangle to the same name, although D will regard them as different symbols. C++ treats extern"C" names the same way.
Hah! so this actually makes me more convinced we should do it differently: cfile.c #include <stdio.h> void cfunction() {printf("hello\n");} file1.d module file1; extern(C) void cfunction(int); file2.d module file2; extern(C) void cfunction(const char *); main.d import file1; import file2; void main() { cfunction(1); cfunction("1"); } This works, and prints "hello" twice. I really think D should consider extern(C) functions as not overloadable, and universally binding (i.e. it's not an error to have 2 identical definitions in separate modules, and it is an error to have 2 different overloads of an extern(C) fucntion). Is there a good reason to do it the current way besides "that's what C++ does"? -Steve
Jan 29 2015
next sibling parent reply Artur Skawina via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 01/29/15 14:43, Steven Schveighoffer via Digitalmars-d wrote:
 I really think D should consider extern(C) functions as not overloadable,
All functions are overloadable in D; ie you can mix C and D overloads freely; this is a feature. It allows you to extend the C i/f without adding an extra layer of pointless wrappers. And export a subset of the D i/f to other C-but-not-D-aware languages. Etc.
 and universally binding (i.e. it's not an error to have 2 identical
definitions in separate modules,
External functions with identical signatures and identical mangled names are obviously not conflicting, so that case does not need to be an error, yes. But I suspect that, in practice, sticking to just one declaration is a good idea anyway (the language changes with every compiler release, so the signatures can easily get out of sync. eg missing nothrow/ nogc/ return attributes). artur
Jan 29 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/29/15 1:35 PM, Artur Skawina via Digitalmars-d wrote:
 On 01/29/15 14:43, Steven Schveighoffer via Digitalmars-d wrote:
 I really think D should consider extern(C) functions as not overloadable,
All functions are overloadable in D; ie you can mix C and D overloads freely; this is a feature. It allows you to extend the C i/f without adding an extra layer of pointless wrappers. And export a subset of the D i/f to other C-but-not-D-aware languages. Etc.
I think I may not have expressed clearly, but I meant that C bindings should not be overloadable with C bindings. Overloading C functions with D functions is perfectly fine.
 and universally binding (i.e. it's not an error to have 2 identical
definitions in separate modules,
External functions with identical signatures and identical mangled names are obviously not conflicting, so that case does not need to be an error, yes. But I suspect that, in practice, sticking to just one declaration is a good idea anyway (the language changes with every compiler release, so the signatures can easily get out of sync. eg missing nothrow/ nogc/ return attributes).
In practice, this is more difficult to control. It's so easy to just throw in a C binding when you need it, especially if you don't know where an existing binding exists, or there isn't a reasonable central place to place it. This is more difficult when you have competing unrelated libraries that you need to use concurrently. To have one corrupt the other, when they are exactly the same binding, doesn't make a whole lot of sense. -Steve
Jan 29 2015
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/29/2015 5:43 AM, Steven Schveighoffer wrote:
 Is there a good reason to do it the current way besides
 "that's what C++ does"?
My reason for saying "that's what C++ does" is that in 30 years I've never heard anyone bring it up as a problem. So while we could go about diagnosing such errors, I regard it as a very low priority.
Jan 29 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/29/15 4:38 PM, Walter Bright wrote:
 On 1/29/2015 5:43 AM, Steven Schveighoffer wrote:
 Is there a good reason to do it the current way besides
 "that's what C++ does"?
My reason for saying "that's what C++ does" is that in 30 years I've never heard anyone bring it up as a problem. So while we could go about diagnosing such errors, I regard it as a very low priority.
Maybe because that's not how it works in C++ (thought I would test): lib1.h: namespace x { extern "C" { void cfunction(); } } lib2.h: namespace y { extern "C" { void cfunction(); } } main.cpp: #include "lib1.h" #include "lib2.h" using namespace x; using namespace y; int main() { x::cfunction(); // OK y::cfunction(); // OK cfunction(); // OK return 0; } -Steve
Jan 30 2015
parent reply Walter Bright <newshound2 digitalmars.com> writes:
Try cfunction() with different parameter types.
Jan 30 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 1/30/15 6:10 PM, Walter Bright wrote:
 Try cfunction() with different parameter types.
OK, I see that was your point. But what do you say about C++ supporting what I suggested and D failing in that regard? -Steve
Feb 02 2015
prev sibling parent "Jonathan Marler" <johnnymarler gmail.com> writes:
On Tuesday, 27 January 2015 at 18:13:36 UTC, Steven Schveighoffer 
wrote:e or the other, even though they are the same.
 I understand the idea behind keeping the lookup rules 
 consistent. But C lookup rules ARE simple.

 I would say if two extern(C) declarations are identical (i.e. 
 same parameter types, same attributes), they don't conflict. 
 What does this break?

 -Steve
This extern(C) thing is a bit odd. With C, you can include as many headers and declare the same function as many times as you want to. D is breaking this functionality. However, some may see this as a feature. Suppose you wanted to optimize where you were declaring your function prototypes by making sure there were no duplications? In C, I can't think of a way to do this. In D however, it looks like this would cause an error. I kinda agree that maybe D should handle it the same way C does, but another thought comes to mind. Say two libraries declared the same extern(C) function but they used different types or different type qualifiers. You would have to go to one of the libraries and have them modify their's to be identical to the other right? Well, the difference now is that instead of them normalizing their declarations you can suggest that everyone use the same modules for declaring extern(C) functions. This may have been impossible to do in an old language but since D is relatively new, this doesn't seem to unreasonable to do. Anyway, just some thoughts...I think this problem may be more complex then it seems.
Jan 29 2015