[swift-evolution] [swift-evolution-announce] [Review] SE-0117: Default classes to be non-subclassable publicly

Leonardo Pessoa me at lmpessoa.com
Mon Jul 11 10:13:24 CDT 2016


Jean, given this proposal it will be possible if the developer of the
library intends so. You'll have to have unsealed classes to be able to
subclass them and unsealed methods so you can override. It is possible
to just allow subclassing without allowing overriding, just like
final.

As for conflicts I don't think so. If you declare a new method with
the same name as an existing one without overriding, it will become a
new method and the base class won't even know that new method exists.
C# allows this but uses the keyword new (instead of override) to
clarify a new method is being introduced instead of the existing one
but as far as I see there is no such need in Swift. I'm also not sure
we can override a method inside an extension but if so, this provides
a new point of extension inside a class that is not subclassable.

L


On 11 July 2016 at 11:21, Jean-Daniel Dupas via swift-evolution
<swift-evolution at swift.org> wrote:
> Just a though, but why sealed classes have to be completely unsubclassable ?
>
> Wouldn't it be possible to allow the user to subclass sealed class, but deny
> overriding of any public member.
>
> I see a use case where a user want to extends an existing model by adding
> new properties and new methods to an object but can’t use composition
> because doing that will prevent to pass that object to the framework that
> expect the base object.
>
> That would let user override existing class to extends them, but should not
> cause any side effect in the way the class should behave, and so would not
> affects preconditions and postconditions, and should not prevent
> optimization in whole module compilation, as the methods of the base class
> are considered final outside of the module.
>
> Of course, it will introduce some fragility in the library, as adding new
> methods may conflict with user subclass methods, but no more than what would
> append if the user write extension to add new methods to the model.
>
> Le 11 juil. 2016 à 05:38, Jordan Rose via swift-evolution
> <swift-evolution at swift.org> a écrit :
>
> [Proposal:
> https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md
> ]
>
> (This is my second response to this proposal. The previous message shared a
> use case where public-but-non-subclassable made things work out much better
> with required initializers. This one has a bit more ideology in it.)
>
> As many people have said already, this proposal is quite beneficial to
> library designers attempting to reason about their code, not just now but in
> the future as well. The model laid out in the Library Evolution document
> (often referred to as “resilience”) supports Swift libraries that want to
> preserve a stable binary and source interface.
>
> In the Swift 2 model (and what’s currently described in that document), a
> public class must be final or non-final at the time it is published. It’s
> clearly not safe to add ‘final' in a later version of the library, because a
> client might already have a subclass; it’s also not safe to remove ‘final’
> because existing clients may have been compiled assuming there are no
> subclasses.
>
> (Of course, we can remove this optimization, and make ‘final’ a semantic
> contract only. I’m deliberately avoiding most discussion of performance, but
> in this parenthetical I’ll note that Swift makes it possible to write code
> that is slower than Objective-C. This is considered acceptable because the
> compiler can often optimize it for a particular call site. For those who
> want more information about the current implementation of some of Swift’s
> features, I suggest watching the “Understanding Swift Performance” talk from
> this year’s WWDC.)
>
> With this proposal, a public class can be non-publicly-subclassable or
> publicly-subclassable. Once a class is publicly-subclassable (“open”), you
> can’t go back, of course. But a class that’s not initially open could become
> open in a future release of the library. All existing clients would already
> be equipped to deal with this, because there might be subclasses inside the
> library. On the other hand, the class can also be marked ‘final’, if the
> library author later realizes there will never be any subclasses and that
> both client authors and the compiler should know this.
>
> One point that’s not covered in this proposal is whether making a class
> ‘open’ applies retroactively, i.e. if MagicLib 1.2 is the first version that
> makes the Magician class ‘open’, can clients deploy back to MagicLib 1.0 and
> expect their subclasses to work? My inclination is to say no; if it’s
> possible for a non-open method to be overridden in the future, a library
> author has to write their library as if it will be overridden now, and
> there’s no point in making it non-open in the first place. That would make
> ‘open’ a “versioned attribute” in the terminology of Library Evolution,
> whatever the syntax ends up being.
>
> ---
>
> Okay, so why is this important?
>
> It all comes down to reasoning about your program’s behavior. When you use a
> class, you’re relying on the documented behavior of that class. More
> concretely, the methods on the class have preconditions
> (“performSegue(withIdentifier:sender:) should not be called on a view
> controller that didn’t come from a storyboard”) and postconditions (“after
> calling loadViewIfNeeded(), the view controller’s view will be loaded”).
> When you call a method, you’re responsible for satisfying its preconditions
> so it can deliver on the postconditions.
>
> I used UIViewController as an example, but it applies just as much to your
> own methods. When you call a method in your own module—maybe written by you,
> maybe by a coworker, maybe by an open source contributor—you’re expecting
> some particular behavior and output given the inputs and the current state
> of the program. That is, you just need to satisfy its preconditions so it
> can deliver on the postconditions. If it’s a method in your module, though,
> you might not have taken the trouble to formalize the preconditions and
> postconditions, since you can just go look at the implementation. Even if
> your expectations are violated, you’ll probably notice, because the conflict
> of understanding is within your own module.
>
> Public overriding changes all this. While an overridable method may have
> particular preconditions and postconditions, it’s possible that the
> overrider will get that wrong, which means the library author can no longer
> reason about the behavior of their program. If they do a poor job
> documenting the preconditions and postconditions, the client and the library
> will almost certainly disagree about the expected behavior of a particular
> method, and the program won’t work correctly.
>
> "Doesn’t a library author have to figure out the preconditions and
> postconditions for a method anyway when making it public?" Well, not to the
> same extent. It’s perfectly acceptable for a library author to document
> stronger preconditions and weaker postconditions than are strictly
> necessary. (Maybe 'performSegue(withIdentifier:sender:)’ has a mode that can
> work without storyboards, but UIKit isn’t promising that it will work.) When
> a library author lets people override their method, though, they're
> promising that the method will never be called with a weaker precondition
> than documented, and that nothing within their library will expect a
> stronger postcondition than documented.
>
> (By the way, the way to look at overriding a method is the inverse of
> calling a method: you need to deliver on the postconditions, and you can
> assume the caller has satisfied the preconditions. If your understanding of
> those preconditions and postconditions is wrong, your program won’t work
> correctly, just like when you’re calling a method.)
>
> This all goes double when a library author wants to release a new version of
> their library with different behavior. In order to make sure existing
> callers don’t break, they have to make sure all of the library’s documented
> preconditions are no stronger and postconditions are no weaker for public
> API. In order to make sure existing subclassers don’t break, they have to
> make sure all of the library’s documented preconditions are no weaker and
> postconditions are no stronger for overridable API.
>
> (For a very concrete example of this, say you’re calling a method with the
> type '(Int?) -> Int’, and you’re passing nil. The new version of the library
> can’t decide to make the parameter non-optional or the return value
> optional, because that would break your code. Similarly, if you’re
> overriding a method with the type ‘(Int) -> Int?’, and returning nil, the
> new version of the library can’t decide to make the parameter optional or
> the return value non-optional, because that would break your code.)
>
> So, "non-publicly-subclassable" is a way to ease the burden on a library
> author. They should be thinking about preconditions and postconditions in
> their program anyway, but not having to worry about all the things a client
> might do for a method that shouldn’t be overridden means they can actually
> reason about the behavior—and thus the correctness—of their own program,
> both now and for future releases.
>
> ---
>
> I agree with several people on this thread that
> non-publicly-subclassable-by-default is the same idea as
> internal-by-default: it means that you have to explicitly decide to support
> a capability before clients can start relying on it, and you are very
> unlikely to do so by accident. The default is “safe” in that a library
> author can change their mind without breaking existing clients.
>
> I agree with John that even today, the entry points that happen to be public
> in the types that happen to be public classes are unlikely to be good entry
> points for fixing bugs in someone else's library. Disallowing overriding
> these particular entry points when a client already can't override internal
> methods, methods on structs, methods that use internal types, or top-level
> functions doesn’t really seem like a loss to me.
>
> Library design is important. Controlling the public interface of a library
> allows for better reasoning about the behavior of code, better security
> (i.e. better protection of user data), and better maintainability. And
> whether something can be overridden is part of that interface.
>
> Thanks again to Javier and John for putting this proposal together.
> Jordan
>
> P.S. There’s also an argument to be made for public-but-not-conformable
> protocols, i.e. protocols that can be used in generics and as values outside
> of a module, but cannot be conformed to. This is important for many of the
> same reasons as it is for classes, and we’ve gotten a few requests for it.
> (While you can get a similar effect using an enum, that’s a little less
> natural for code reuse via protocol extensions.)
>
> P.P.S. For those who will argue against “better security”, you’re correct:
> this doesn’t prevent an attack, and I don’t have much expertise in this
> area. However, I have talked to developers distributing binary frameworks
> (despite our warnings that it isn’t supported) who have asked us for various
> features to keep it from being easy.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


More information about the swift-evolution mailing list