Hello!
I sent this as a PR <https://github.com/apple/swift-evolution/pull/376> on
the swift-evolution repo, but we never had any discussion about it on-list,
besides a long time ago
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/9702/focus=9708>\.
Here's the first draft of the proposal:
Sealed classes by default
<swift-evolution/0000-sealed-by-default.md at a46877afb0302d2b03fa493255f5ced04ccb7f34 · JaviSoto/swift-evolution · GitHub;
Introduction
Introduce a new sealed class modifier that makes classes and methods
final outside
of the module they're declared in, but non-final within the module.
<swift-evolution/0000-sealed-by-default.md at a46877afb0302d2b03fa493255f5ced04ccb7f34 · JaviSoto/swift-evolution · GitHub;
Motivation
- It is not uncommon to have a need for a reference type without needing
inheritance. Classes must be intentionally designed to be subclassable,
carefully deciding which methods are the override entry-points such that
the the behavior remains correct and subclasses respect the Liskov
substitution principle
<https://en.wikipedia.org/wiki/Liskov_substitution_principle>\.
- Defaulting to non-final allows the author of a class to accidentally
leave the visible methods open for overrides, even if they didn't carefully
consider this possibility.
- Requiring that the author of a class mark a class as open is akin to
requiring symbols to be explicitly public: it ensures that a conscious
decision is made regarding whether the ability to subclass a class is
part of the API.
- New sealed (*actual name pending bike-shedding*) class modifier for
classes and methods which marks them as only overridable within the module
they're declared in.
- sealed becomes the default for classes and methods.
- New open (*actual name pending bike-shedding*) class modifier to
explicitly mark a class or a method as overridable.
Code Examples:
/// ModuleA:
/// This class is `sealed` by default./// This is equivalent to
`sealed class SealedParentClass`class SealedParentClass {
/// This method is `sealed` by default`.
func foo()
/// This raises a compilation error: a method can't have a "subclassability"
/// level higher than that of its class.
open func bar()
/// The behavior of `final` methods remains unchanged.
final func baz()
}
open class OpenParentClass {
/// This method is `sealed` by default`.
func foo()
/// Overridable methods in an `open` class must be explicitly
marked as `open`.
open func bar()
/// The behavior of a `final` method remains unchanged.
final func baz()
}
/// The behavior of `final` classes remains unchanged.final class FinalClass { }
/// ModuleB:
import ModuleA
/// This raises a compilation error: ParentClass is effectively
`final` from/// this module's point of view.class SubclassA :
SealedParentClass { }
/// This is allowed since `OpenParentClass` has been marked explicitly
`open`class SubclassB : OpenParentClass {
/// This raises a compilation error: `OpenParentClass.foo` is
/// effectively `final` outside of `ModuleA`.
override func foo() { }
/// This is allowed since `OpenParentClass.bar` is explicitly `open`.
override func bar() { }
}
<swift-evolution/0000-sealed-by-default.md at a46877afb0302d2b03fa493255f5ced04ccb7f34 · JaviSoto/swift-evolution · GitHub
on existing code
- This would be a backwards-breaking change for all classes and methods
that are public and non-final, which code outside of their module has
overriden. Those classes/methods would fail to compile. Their superclass
would need to be changed to open.
- Defaulting to final instead: This would be comparable to Swift
defaulting to private, as opposed to internal. Just like internal is a
better trade-off, sealed by default also makes sure that getting started
with Swift, writing code within a module, doesn't require a lot of
boilerplate, and fighting against the compiler.
···
--
Javier Soto