<div dir="ltr">Hello!<div><br></div><div>I sent this as a <a href="https://github.com/apple/swift-evolution/pull/376">PR</a> on the swift-evolution repo, but we never had any discussion about it on-list, besides <a href="http://thread.gmane.org/gmane.comp.lang.swift.evolution/9702/focus=9708">a long time ago</a>. Here's the first draft of the proposal:</div><div><br></div><div><br></div><div><h1 style="font-size:2.25em;margin:0px 0px 16px;line-height:1.2;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol"">Sealed classes by default</h1><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-introduction" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#introduction" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Introduction</h2><p style="margin-top:0px;margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px">Introduce a new<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">sealed</code><span class="inbox-inbox-Apple-converted-space"> </span>class modifier that makes classes and methods<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">final</code><span class="inbox-inbox-Apple-converted-space"> </span>outside of the module they're declared in, but non-<code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">final</code><span class="inbox-inbox-Apple-converted-space"> </span>within the module.</p><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-motivation" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#motivation" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Motivation</h2><ul style="padding-left:2em;margin-top:0px;margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px"><li style="box-sizing: border-box;">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<span class="inbox-inbox-Apple-converted-space"> </span><a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" style="color:rgb(64,120,192);text-decoration:none;background-color:transparent">Liskov substitution principle</a>.</li><li style="box-sizing: border-box;">Defaulting to non-<code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">final</code><span class="inbox-inbox-Apple-converted-space"> </span>allows the author of a class to accidentally leave the visible methods open for overrides, even if they didn't carefully consider this possibility.</li><li style="box-sizing: border-box;">Requiring that the author of a class mark a class as<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">open</code><span class="inbox-inbox-Apple-converted-space"> </span>is akin to requiring symbols to be explicitly<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">public</code>: it ensures that a conscious decision is made regarding whether the ability to subclass a<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">class</code><span class="inbox-inbox-Apple-converted-space"> </span>is part of the API.</li></ul><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-proposed-solution" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#proposed-solution" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Proposed solution</h2><ul style="padding-left:2em;margin-top:0px;margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px"><li style="box-sizing: border-box;">New<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">sealed</code><span class="inbox-inbox-Apple-converted-space"> </span><span style="line-height:25.6px">(</span><em style="line-height:25.6px">actual name pending bike-shedding</em><span style="line-height:25.6px">) </span>class modifier for classes and methods which marks them as only overridable within the module they're declared in.</li><li style="box-sizing: border-box;"><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">sealed</code><span class="inbox-inbox-Apple-converted-space"> </span>becomes the default for classes and methods.</li><li style="box-sizing: border-box;">New<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">open</code><span class="inbox-inbox-Apple-converted-space"> </span>(<em style="box-sizing: border-box;">actual name pending bike-shedding</em>) class modifier to explicitly mark a class or a method as<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">overridable</code>.</li></ul><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-detailed-design" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#detailed-design" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Detailed design</h2><p style="margin-top:0px;margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px">Code Examples:</p><div class="inbox-inbox-highlight inbox-inbox-highlight-source-swift" style="margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px"><pre style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;margin-top:0px;margin-bottom:0px;font-stretch:normal;line-height:1.45;word-wrap:normal;padding:16px;overflow:auto;border-radius:3px;word-break:normal;background-color:rgb(247,247,247)"><span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// ModuleA:</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This class is `sealed` by default.</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This is equivalent to `sealed class SealedParentClass`</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">class</span> SealedParentClass {
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This method is `sealed` by default`.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">foo</span>()
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This raises a compilation error: a method can't have a "subclassability"</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// level higher than that of its class.</span>
open <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">bar</span>()
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// The behavior of `final` methods remains unchanged.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">final</span> <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">baz</span>()
}
open <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">class</span> OpenParentClass {
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This method is `sealed` by default`.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">foo</span>()
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// Overridable methods in an `open` class must be explicitly marked as `open`.</span>
open <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">bar</span>()
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// The behavior of a `final` method remains unchanged.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">final</span> <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">baz</span>()
}
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// The behavior of `final` classes remains unchanged.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">final</span> <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">class</span> FinalClass { }</pre></div><div class="inbox-inbox-highlight inbox-inbox-highlight-source-swift" style="margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px"><pre style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;margin-top:0px;margin-bottom:0px;font-stretch:normal;line-height:1.45;word-wrap:normal;padding:16px;overflow:auto;border-radius:3px;word-break:normal;background-color:rgb(247,247,247)"><span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// ModuleB:</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">import</span> <span class="inbox-inbox-pl-c1" style="color:rgb(0,134,179)">ModuleA</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This raises a compilation error: ParentClass is effectively `final` from</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// this module's point of view.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">class</span> SubclassA <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">:</span> SealedParentClass { }
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This is allowed since `OpenParentClass` has been marked explicitly `open`</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">class</span> SubclassB <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">:</span> OpenParentClass {
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This raises a compilation error: `OpenParentClass.foo` is</span>
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// effectively `final` outside of `ModuleA`.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">override</span> <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">foo</span>() { }
<span class="inbox-inbox-pl-c" style="color:rgb(150,152,150)">/// This is allowed since `OpenParentClass.bar` is explicitly `open`.</span>
<span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">override</span> <span class="inbox-inbox-pl-k" style="color:rgb(167,29,93)">func</span> <span class="inbox-inbox-pl-en" style="color:rgb(121,93,163)">bar</span>() { }
}</pre></div><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-impact-on-existing-code" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#impact-on-existing-code" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Impact on existing code</h2><ul style="padding-left:2em;margin-top:0px;margin-bottom:16px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px"><li style="box-sizing: border-box;">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<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">open</code>.</li></ul><h2 style="margin-top:1em;margin-bottom:16px;line-height:1.225;font-size:1.75em;padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:rgb(238,238,238);color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol""><a id="inbox-inbox-user-content-alternatives-considered" class="inbox-inbox-anchor" href="https://github.com/JaviSoto/swift-evolution/blob/a46877afb0302d2b03fa493255f5ced04ccb7f34/proposals/0000-sealed-by-default.md#alternatives-considered" style="color:rgb(64,120,192);text-decoration:none;display:inline-block;padding-right:2px;line-height:1;background-color:transparent"></a>Alternatives considered</h2><ul style="padding-left:2em;margin-top:0px;color:rgb(51,51,51);font-family:"helvetica neue",helvetica,"segoe ui",arial,freesans,sans-serif,"apple color emoji","segoe ui emoji","segoe ui symbol";font-size:16px;line-height:25.6px;margin-bottom:0px"><li style="box-sizing: border-box;">Defaulting to<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">final</code><span class="inbox-inbox-Apple-converted-space"> </span>instead: This would be comparable to Swift defaulting to<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">private</code>, as opposed to<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">internal</code>. Just like<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">internal</code><span class="inbox-inbox-Apple-converted-space"> </span>is a better trade-off,<span class="inbox-inbox-Apple-converted-space"> </span><code style="font-family:consolas,"liberation mono",menlo,courier,monospace;font-size:13.6px;padding:0.2em 0px;margin:0px;border-radius:3px;background-color:rgba(0,0,0,0.0392157)">sealed</code><span class="inbox-inbox-Apple-converted-space"> </span>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.</li></ul></div></div><div dir="ltr">-- <br></div><div data-smartmail="gmail_signature"><div dir="ltr">Javier Soto</div></div>