Should anyone be using @_silgen_name outside of the standard library developers?

Interfacing with external C definitions in their full generality requires the full generality of C. Swift doesn't have a way to express everything a C declaration can, nor would we really want it to.

3 Likes

Swift has historically partially supported things – take the opaque type system. If something as incomplete as that can be shipped, why can't there be a sanctioned way of declaring externs with something like @_silgen_name?

Is it terribly difficult to set up a bridging header? No. Is it annoying? Yes. It adds another layer of complexity, and unnecessarily so.

For myself, and presumably many others, @_silgen_name has been enough every time I've needed to link to a C function. I think that in the majority of times, we are not interfacing with external C definitions in their full generality. Rather, we are interfacing with them in one-off scenarios where it could be quite a simple signature.

I'm willing to accept that writing these externs requires perfectly precise types, UnsafePointer and all. I think it's a valuable feature to be able to quickly interface with C without going through the chaos of adding bridging headers and going back and forth between C headers and Swift code, even if there will be cases where a definition is simply not able to be defined within the constructs of Swift.

I've got some examples from the Swift repo that show how inline externs can improve both readability and complexity. Although they are from tests, it still goes to show that the authors see the merit in using _silgen_name for non-Swift-CC externs.

Rather than doing the whole shebang of creating a bridging header where they chucked _NSGetEnviron, they declared an extern right above where it's called. You can clearly see where it is declared, where it is used, and why it is needed.

Unsure why it was even needed, but that's the point – the author was in a pickle, they quickly wrote out an extern, and they were on their way. No need to mess with a bridging header.

objc_autorelease is straight up not exposed, and instead of chucking it into a bridging header, it was declared inline right where it's used.

I get that these aren't the best examples, and they honestly might be kinda bad. But I hope they show the value in being able to concisely and quickly declare external functions from within Swift, without the need for bridging headers or mangling the type of a dlsym'd function.

I also understand that this is a pretty unsafe language feature – but I don't think that's reason enough to leave us in a state where this currently-working solution could be broken without warning or replacement that doesn't involve bridging headers.

@_silgen_name is fundamentally broken for the purpose. It's unfortunate that it pretends to work in so many cases, and that it's so easy to reach for if you know it exists, but please don't. It will break. All of those places you raised in the Swift repo are misusing it too and need to be fixed. I can sympathize with the desire for an easy way to declare an external C function, but @_silgen_name is not it, and will never be it. I would not object to someone implementing something like maybe allowing @_cdecl to declare a function without a body and proposing that as a feature.

6 Likes

Thank you for seeing the benefits, even if _silgen_name isn't suited for the challenge. I'd love to take a stab at getting this behavior implemented in _cdecl, although I haven't done any development for Swift itself before. I'll give it a shot this weekend.

I agree that _silgen_name is not nor never was intended to be used in such a manner, but the reality is that developers have discovered this side effect and exploited it en masse. Because of this, it would be helpful if there was some kind of explicit warning shown to non-stdlib usages to deter further misuse before any breaking changes are made.

I would like to bump this thread because I have a use case for _silgen_name that was not mentioned in this thread that has nothing to do with C interop that I think deserves a mention: allowing a module to reference/call code from modules that it doesn't depend on, for the purpose of improving build times.

/// Module A
@_silgen_name("my_module_a_private_function"), or @_cdecl
func myPrivateFunction() { ... }

/// Module B (does NOT import Module A, but IS statically linked to it in the resulting app binary)
@_silgen_name("my_module_a_private_function")
func refToPrivateFunctionFromModuleA()

func foo() {
    refToPrivateFunctionFromModuleA()
}

Yes, this is an absolute travesty, the attribute may change, mangling may change, I have to do manual memory management, the code is super fragile, I have to account for compiler optimizations, etc etc.

But my point is: I am fine with it. We do not need to this; module B could very well import module A. But in a project like ours where dependency injection is handled in compile-time and the number of modules is in the thousands, the module that is responsible for wiring dependencies can get so massive that invalidating it in any way can easily result in the developers waiting 10+ seconds for it to compile again. By having the generated wiring code refer to the other modules via _silgen_name instead of directly depending on them, we can get a massive improvement to incremental build times in exchange for making the wiring code dangerous. But again, I am fine with this; we have full control of the inputs and outputs and we are used to doing this sort of stuff.

Absolutely understand that the attribute is not supposed to be used like this. I think my conclusion though is that I think there is a real demand for "power user / I know this is dangerous, let me do it anyway" features like this in Swift, and it would be amazing if _silgen_name could be recognized as one such feature and supported officially. It's very powerful, I would love to be able to use it without fear that it might change or stop existing in the future.

You might be interested in the linkage control pitch then. But for what you're talking about, it seems to me like a better approach would be for us to support user-written swiftinterface files for modules, allowing dependencies to be written against the explicit interface, and the module implementation checked against the explicit interface separately.

4 Likes

Header file enthusiasts rejoice!

2 Likes