Why can't I have a private @usableFromInline declaration?

The @usableFromInline annotation can only be attached to internal declarations. Why can't I attach it to private ones too? Whether it's usable from inline should be completely orthogonal to access control (except in that public implies usable from inline already).

For context, I've got a property that I want to make private(set), because I need to preserve certain invariants when modifying it, but I also want to make a public function that modifies it @inlinable.

1 Like

I believe the reason is the exported name. The symbol for the exported function is exposed in the library's ABI, which means it needs a name, and that name must be unique. Private functions on the other hand can have multiple definitions with the same name living in different files. So @usableFromInline would have to change the semantics of private to make it an error to have two private symbols that are @usableFromInline with the same name in the same library that are both @usableFromInline.

I think it's achievable, but the resulting semantics are a bit weird and unexpected in other ways.

5 Likes

That's a good point. But it's a very frustrating limitation, and I'd much rather have the limitation of "you can't have 2 @usableFromInline private symbols with the same name.

Also, in my case I don't even care about ABI stability, as the symbol is declared in a framework that I bundle in my app. So this limitation serves no benefit to me.

In any case, if we changed private symbols to ditch the UUID if they're declared within the original type declaration instead of an extension, then we could at least relax it to "@usableFromInline cannot be used on a private symbol inside of an extension", which is pretty similar to "you can't declare stored proprties in an extension". I'd definitely prefer the restriction to be "you can't have two @usableFromInline private symbols with the same name", but the "it can't be in an extension" rule might be conceptually simpler.

@michelf pretty much got it. Allowing @usableFromInline on private declarations would mean doing redeclaration checking across files of declarations nominally marked private, perhaps nested in extensions. I don't think it's impossible but it would definitely break assumptions in the compiler.

(Alternately, the compiler could just believe you, and then you'd get a linker or SIL error if you were wrong. That might be easiest in the long run.)

4 Likes

Just to clarify, you can't export duplicate symbol names in a library; whether its ABI is stable or not doesn't change this.

I think Lily's point was that the mangled names could continue to have discriminators in them if they didn't have to be stable. LLDB can find them just fine, for example. But it'd be tricky if we ever wanted to support module interface files with non-library-evolution-enabled frameworks, though I'm not sure we will.

1 Like

How that @inlinable requires at least internal access level for its calls but @usableFromInline doesn't?

public struct Foo {

    @inlinable
    public var bar: Int {
        get { baz }
        set { baz = newValue }
    }

    @usableFromInline
    internal var baz: Int {
        get { qux }
        set { qux = newValue }
    }

    private var qux: Int
}

Would foo.bar = .zero inline down up to qux or just to baz that will call qux?

The body of an @inlinable function is exposed to clients, so it can only reference public and @usableFromInline declarations.

However @usableFromInline imposes no restrictions on the declaration's body.

Just to make sure I understand it correctly, it means that the inlining (if the compiler chooses to inline) takes only the exposed body, so bar and baz get inlined, but the inlined code still makes a call to qux?

It depends on if the inlining is happening while building the original module, or while building a client of the module. In the original module, code generation can "see" all function bodies. In a client module, only inlinable bodies are available. @usableFromInline declarations can then be referenced from the client module via inlining, but their bodies remain opaque.

I'm interested about building a client of the module.
What does opaque body mean? Inlined but unable to optimize?

It means it's not visible to the optimizer. For example, if I have this code in module A:

@inlinable
public func f() { g() }

public func g() { print("Hello world") }

And this in module B:

import A

f()

Then B will call g(), but the body of g() is not visible while compiling B.

1 Like

Got you! Thanks Slava!

1 Like