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

Xiaodi Wu xiaodi.wu at gmail.com
Sun Jul 10 23:38:10 CDT 2016


On Sun, Jul 10, 2016 at 10:38 PM, Jordan Rose via swift-evolution <
swift-evolution at swift.org> wrote:

> [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 <http://jrose-apple.github.io/swift-library-evolution/> (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
> <https://developer.apple.com/videos/play/wwdc2016/416/>” 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
> <http://jrose-apple.github.io/swift-library-evolution/#publishing-versioned-api>”
> 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.)
>

Would public-but-not-conformable protocols by default be the next step,
then, in Swift's evolution?


> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160710/4543f23b/attachment.html>


More information about the swift-evolution mailing list