I really wish someone on the C language/compiler/linker level took a real look at the problem and actually tried to solve it in a way that isn't a pain to deal with for people that integrate with the code.
I really wish someone on the C language/compiler/linker level took a real look at the problem and actually tried to solve it in a way that isn't a pain to deal with for people that integrate with the code.
It exists as "inline" and "extern inline".[1] Few people make use of them, though, partly because the semantics standardized by C99 were the complete opposite of the GCC extensions at the time, so for years people were warned to avoid them; and partly because linkage matters are a kind of black magic people avoid whenever possible--"static inline" neatly avoids needing to think about it.
[1] See C23 6.7.5 at https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#p...
Sure, except when you do FFI bindings. Then those functions just don't exist and you get a linking error.
// a.c
int f( int x ) {
return x + 1;
}
// b.c
extern int f(int x );
int main() {
int y = f(41);
}
but if f() had been defined as static, you couldn't do this.Just because you can use the information you have to call a given function, doesn't mean you aren't violating an interface.
For most purposes, not being able to access something, and being able to access something not officially in an interface, where doing so introduces an unpredictable breaking dependency, the practical result is the same: You can't (actually/sensibly) do it.
The whole point of using "static" in that way is to prevent people from using it outside of the file.
If you need to call a static function (inline or otherwise) from outside of the compilation unit to use the API, then it's a bug in the API, not a problem with static.
I agree with you about pre-processor macros, though.
int Foo(int a, ...);
But in fact the types are restricted depending on a call type, set by 'a'. So I could add a wrapper for a specific variant: static inline FooVar1(char b, long c)
{
return Foo(FOO_VAR_1, b, c);
}
All the wrapper does is adds a bit of type control during compilation, otherwise it must be just a function call to 'Foo'. It is like a supermacro. It does make sense to put that into 'static inline' in a header.It also does not have to do anything with the language, because it IS possible to have FooVar1 as part of the interface. The library developer just didn't want that, likely because they do not want tons of slightly different functions in the interface that they would need to maintain, when they already exposed that functionality.
EDIT:
Concerning your earlier complaint, that you would need to
> have to redo subsequent version updates in your code.
, such a change would be ABI-incompatible anyway and demand a change in the major version number of the library. Neither your FFI, nor any program even when written in C, is going to work anymore. The latter, would just randomly crash, not sure about the former.
Note, that you also see here, where the real interface is. A change to Foo itself, would break programs written in C as well as your FFI, while a change to FooVar1 wouldn't matter at all. Both C programs and your FFI will continue to run just fine without any changes.
No. They do it because it avoids the overhead of dynamic dispatch. Nothing else.
So when I've seen these in reality in real library interfaces they have not been 'example wrappers'. They are real intended usage of the API, and in some cases the lib authors recognize that it is a problem and even provide ways to build it without the issues - either by compile time flag or always outputting two variants of the .so. The former moves the question to the distro - should they provide the version optimized for C or the one optimized for other languages? In the case of the latter it obviously creates a situation where the vast majority of both .so-s are duplicated.
As an example of the compile flag, there is libpipewire. As an example of providing two versions of the .so, there is liburing.
So no, this is clearly something the language ecosystem lacks a good answer to. There is currently no way to make everyone happy.
> such a change would be ABI-incompatible anyway
True, but the C based ones can at least simply rebuild and the problem would go away.
Example: https://github.com/axboe/liburing/blob/master/src/include/li...
Do you have a relevant example?
Thankfully that particular library do nowadays also provide a _ffi version of the .so as it is such an horror otherwise. But there are other libraries out there that don't do that, where it is almost as big of a hurdle to bind with.