[swift-evolution] Enums and Source Compatibility

Vladimir.S svabox at gmail.com
Thu Sep 7 09:24:26 CDT 2017



On 07.09.2017 16:03, Rod Brown wrote:
> 
> 
>> On 7 Sep 2017, at 9:26 pm, Vladimir.S via swift-evolution 
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> On 07.09.2017 7:33, Chris Lattner via swift-evolution wrote:
>>>> On Sep 5, 2017, at 5:19 PM, Jordan Rose via swift-evolution 
>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org> 
>>>> <mailto:swift-evolution at swift.org>> wrote:
>>>>
>>>> I've taken everyone's feedback into consideration and written this up as a 
>>>> proposal: 
>>>> https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums/proposals/nnnn-non-exhaustive-enums.md. 
>>>> The next step is working on an implementation, but if people have further 
>>>> pre-review comments I'd be happy to hear them.
>>> Hi Jordan,
>>> I apologize in advance that I haven’t followed the back and forth on this thread, 
>>> so I’m sorry if these thoughts are duplicative:
>>> I really would prefer to avoid introducing the notion of exhaustive/nonexhaustive 
>>> enums into Swift, and would much prefer that such a thing be limited to C (which 
>>> can’t express this concept already).
>>> We’ve talked about enums many times across years, and it seems like the 
>>> appropriate model follows the generally understood resilience model. Specifically, 
>>> there should be three different kinds of enums, and the kind should affect users 
>>> outside their module in different ways:
>>> 1. private/fileprivate/internal enum: cases can be added freely.  All clients are 
>>> in the same module, so the enum is implicitly fragile, and all switches within the 
>>> current module may therefore be exhaustive.
>>> 2. public enum (i.e., one that isn’t marked fragile): cases may be added freely.   
>>> Within the module that defines the enum, switches may be exhaustive.  However, 
>>> because the enum is public and non-fragile, clients outside the current module 
>>> must be prepared for the enum to add additional cases in future revisions of the 
>>> API, and therefore they cannot exhaustively match the cases of the enum.
>>
>> Just small note. As I understand, this is a source breaking suggestion, no?
>> I mean any client code for 'public enum' coming from another module, will have to 
>> add 'default' case in 'switch'. So, the previously correct code will not compile. 
>> Or do you suggest to raise a warning only for the first time and raise an error in 
>> next version of Swift?
> 
> Yes. By defaulting to “non fragile” we add a source incompatibility, because Swift is 
> currently assuming fragility. In Jordan’s proposal, it is assuming 
> fragility/exhaustive by default, and therefore would be source compatible. Good point.
> 
> I keep wavering back and forth on the importance of Source Compatibility with this 
> one. Defaulting to “exhaustive” seems dangerous to me. It makes your framework (and 
> future versions of it) fragile without ever having to think about it. At least if the 
> keyword of “exhaustive” or “fragile” or “sealed” meant that you actively chose the 
> fragility that will handcuff you later down the road.
> 

Yes, probably(still thinking about this) I agree that 'open'('exhaustive') enum 
should be the default for public enum; and only if author is really sure enum will 
not change in future and to provide a space for compiler's optimizations and suggest 
client to switch exhaustive, he/she can mark such enum as 
exhaustive/fragile/sealed/closed/fixed. And seems like the right direction is raise 
warnings fist, and errors in next version of Swift.

The only question I can't find strong answer for, what my code(as a client of 'open' 
enum) will do in 'default' case? Will it be in 95% cases the fatalError().. If so, 
what is the difference with current situation?
If my switch is already exhaustive regarding some public(imported) enum, so I'm 
processing all the possible *at the compilation time* cases - most likely I'll have 
just fatalError() in 'default'.
If my switch already contains 'default' - the proposed change will not affect me at 
all...

But probably it is better to have a choice what to do with future cases, than have 
just one direction - crash at run time if new case in external enum is added.

But the same I can say for proposed 'future' case : it is better to have a choice 
what to use - 'default', if we don't need to be exhaustive on 'open' enum,  or 
'future', if we need to be exhaustive on 'open' enum in compile-time but separately 
process all future cases.

Yes, in this case we have a problem with testability, but the same is true for 
exhaustive switch with 'defailt' case - how this 'default' code can be tested? 
Probably, we need to provide a solution for this in any case, not because of 'future' 
but also for 'default' in exhaustive switch on 'open' enum. (Don't know how this 
could be implemented, probably by somehow be able to extend the imported open enum 
with 'fake' case when compile in special mode for testing and be able to send this 
fake case into tested code)

Vladimir.

>>
>>> 3. fragile public enum: cases may not be added, because that would break the 
>>> fragility guarantee.  As such, clients within or outside of hte current module may 
>>> exhaustively match against the enum.
>>
>> I think 'fragile' word does not reflect what is guaranteed for this enum. The 
>> author guaranteed that this enum will not be changed, not "this enum can broke your 
>> code". Can't we use 'sealed'/'closed'/'fixed' here?
>>
>> Vladimir.
>>
>>> This approach gives a very natural user model: app developers don’t have to care 
>>> about enum resilience until they mark an enum as public, and even then they only 
>>> have to care about it when/if they mark an enum as public.  This also builds on 
>>> the notion of fragility - something we need for other nominal types like structs 
>>> and classes - so it doesn’t introduce new language complexity.  Also such an 
>>> approach is entirely source compatible with Swift 3/4, which require defaults 
>>> (this isn’t an accident, it follows from the anticipated design).
>>> This approach doesn’t address the problem of what to do with C though, because C 
>>> doesn’t have a reasonable notion of “extensible” vs “nonextensible” enum.  As 
>>> such, we definitely do need an attribute (or something) to add to Clang.  I think 
>>> that your proposal for defaulting to “extensible” and using 
>>> __attribute__((enum_extensibility(closed))) override this is perfectly sensible.
>>> -Chris
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 


More information about the swift-evolution mailing list