While upgrading to Swift4
from Swift3
, I got some issues related to access control
.
Here is the sample code. Which was there in Swift3
, working fine in past times -
open class MyClass {
private let value: Int
static var defaultValue: Int { return 10 }
public init(value: Int = MyClass.defaultValue) {
self.value = value
}
}
To make the code run in Swift4
, I have to change access control
for defaultValue
to public
.
Here is the Swift4
, compiling version
open class MyClass {
private let value: Int
static public var defaultValue: Int { return 10 }
public init(value: Int = MyClass.defaultValue) {
self.value = value
}
}
While I was wondering what is going on, I tried to remove open
access control for MyClass
, it allowed me to remove access
identifier for defaultValue
. Even can put it to private
.
class MyClass {
private let value: Int
private static var defaultValue: Int { return 10 }
public init(value: Int = MyClass.defaultValue) {
self.value = value
}
}
I understand all the access identifiers, but I am not able to understand this behaviour. Especially the first case where xcode
forced me to change access control
of defaultValue
to public
.
Please help.
My original answer (shown below) is now mostly outdated – the beginnings of the resilience model are to be implemented in Swift 4.2 with the introduction of the
@inlinable
and@usableFromInline
attributes, corresponding to the old@_inlineable
and@_versioned
attributes.In addition, and more importantly, the rule for what default arguments of publically accessible functions can reference has changed again. To recap the previous rules:
In Swift 3 there was no enforcement of what access level such default argument expressions could reference (allowing your first example where
defaultValue
isinternal
).In Swift 4, such a default argument could only refer to declarations exposed as a part of the module's interface, including those that aren't otherwise directly visible to users in another module (i.e
@_versioned internal
).However in Swift 4.2, with the implementation of SE-0193, the rule is now that the default argument expression of a publicly accessible function can only refer to publicly accessible declarations (not even
@inlinable internal
or@usableFromInline internal
).I believe this is paving the way for the displaying of default argument expressions in a module's generated interface file. Currently Swift just shows an unhelpful
= default
, but I believe this will change to actually show the default argument. This can only realistically happen with this new access-control restriction in place (Edit: This is now happening).Old answer (Swift 4)
This change is due to the work towards a resilience model that is already available via underscored attributes (
@_inlineable
,@_versioned
,@_fixed_layout
), but is yet to be officially finalised (so you probably shouldn't be using these attributes yourself yet). You can read about the full proposed details of the resilience model here, as well as the the Swift evolution discussion on it here.In short, an inlineable function is one whose implementation, as well as declaration, is exposed as a part of a module's interface and can therefore be inlined when called from another module. An inlineable function must therefore also be publically accessible to begin with (i.e
public
or higher).What you're running into is a change that makes default argument expressions for publically accessible functions inlineable, meaning that they must be available to be evaluated directly in the calling module's binary. This reduces the overhead of calling a function with default parameter values from another module, as the compiler no longer needs to do a function call for each default argument; it already knows the implementation.
I don't believe this change is officially documented in the release of Swift 4 itself, but it is confirmed by Swift compiler engineer Slava Pestov, who says:
So if you have a publically accessible function with a default argument expression (such as
MyClass.defaultValue
in your case), that expression can now only refer to things that are also a part of that module's interface. So you need to makedefaultValue
publically accessible.Unfortunately, there's currently no way to make a
private
function's declaration part of a module's interface (which would allow for your usage of it in a default argument expression). The attribute that would facilitate this is@_versioned
, but it is forbidden with(file)private
due to the following reasons given by Slava Pestov:You could achieve this with a
@_versioned var defaultValue
though:The declaration of
MyClass.defaultValue
is now exported as a part of the module's interface, but still cannot be directly called from another module's code (as it'sinternal
). However, the compiler of that module can now call it when evaluating the default argument expression. But, as said earlier, you probably shouldn't be using an underscored attribute here; you should wait until the resilience model has been finalised.