Recorded – watch the full episode on YouTube.
We're glad to see
Result in Swift 5, but what took so long to add it and what actually made it happen in the end?
Chris Lattner: Well, there's this amazing new research paper on
Result that was published in 2019 and so we realized when reading that paper it was a good... just kidding. Okay, coming back to social problems. It's not just about technical things. Design is hard. Social differences are difficult to work through. To me, it was very clear early on that
Result was a very important thing to have as part of the language. I think we discussed adding it with Swift 2. So Swift 2 is when error handling came in.
“One of the controversial and undecided things about Swift error handling is that errors in Swift are not typed."
I think that at the time, Swift 2 was also a very hot mess and fast ride towards its own release because it was a very large scope release, and so I think that
Result just got pushed out of that release and so it just missed that. Now when error handling came in, that took some time for the community to adopt it, understand it, get implementation experience and so there's some early discussion about, “oh, well, maybe we don't need
I think there are other people who were arguing, "We do need
catch are one thing but when you need to shuttle something across threads or you want to do these other things, you need a convenient way to express this." And I thought that was pretty unambiguous because people were doing it all the time.
So there was a discussion about how we do this, and here's where you get to the social challenge. One of the controversial and undecided things about Swift’s error handling is that errors in Swift are not typed. Instead, they type erase to
Error and that is absolutely the right thing to do for large-scale frameworks, like UIKit or AppKit – Apple-scale, long-term evolution frameworks, because many of us that have worked on Java and similar things like that see the problems that come about with very specifically typed errors when you do library evolution. And so you want to add networking into the middle of a thing, okay, well now I can start producing network errors and the caller was never prepared for that. So now what do you do? There's lots of bad history and lots of damage that many people are facing this.
"Today Swift does not have typed errors. However, there are other domains, and Swift attempts to solve for multiple different domains where you do know exactly what kind of area you're throwing."
And so today Swift does not have typed errors. However, there are other domains, and Swift attempts to solve for multiple different domains where you do know exactly what kind of area you're throwing. Well, let's pick a simple example, like to Unix syscalls. There is an ironclad guarantee that
open() will only return certain classes of errors. You cannot add new things to that, you just cannot, that's the thing.
There are also use cases for error handling where you're using it more as a control flow mechanism than as propagating a high-level error from a user down through my code. In those cases, again, it's not about reflecting user-visible, localized messages, it's about how do I reflect the fact that I have normal control and unusual control.
And so in these cases, I think the typed errors are absolutely appropriate, and sure, there's an opportunity for misuse and like, "Oh my god, we may fall into the typing all the things and end up in Java framework land." But I think that the odds of that are extremely low and I'm not personally concerned about that.
And so a lot of the discussion in
Result came down to this inherent tension. Do we have typed errors or not? Are we going to have typed errors? Because that's one of the things that is an open point of evolution is, we could add typed errors at any point in time, there's a natural way to do that, and it would be very clean and consistent. And so there's this question of resource allocation prioritization.
Resultto be consistent and contiguous with the error handling approach, but we don't know what your handling approach ultimately will be."
On the one hand, somebody has to be willing to implement it, combined with, then, if somebody is willing to implement it, is it a good thing? Would we say no? And so with
Result, basically, a lot of that discussion came down to, fine, we may or may not do this with throws.
So what should we do with the
Result? We wanted
Result to be consistent and contiguous with the error handling approach, but we don't know what your handling approach ultimately will be. And so, that was a lot of that discussion.
And so, for what it’s worth, I'm very happy with how
Result ended up. So with
Result, you can type the error. And so you can type it to
Error, so you can type erase it yourself, if you want to. And you can be very contiguous with the existing error handling approach. And so I think that's a good design.
Result gets used for more than just
throws. It gets used for lots of things, asynchronous things for foreign APIs, other stuff like that. And so I think that it's clearly the right thing to do.
So I don't know if that answers the question, but I also think that getting typed errors into Swift would be a very, very, very good thing for, admittedly, a narrow class of users that are important, but are maybe not the most important thing right now.
Paul Hudson: Well, as you say, there's an easy way of getting in it pretty much. You have throws right now, you'd leave that in place, leave it completely untyped, but also
throws SomeError. And it becomes optional if you know for your particular case, bang, it works, great. But it leaves it... or your UIKit app, like you said, could just carry on cruising along happily without those things.
"When you throw an error in Swift, one of the things we put great effort into is making it so that throws in Swift is not like exceptions in Java or C++. Producing errors are actually relatively efficient."
Chris Lattner: Yes, and I mean, the advantage of type throws is really twofold. One is, you get the type safety. So when you have your cash clause, you you can enumerate the different enum cases or whatever it is that you're cashing, things like that. You get specificity in your errors. And if you know you're only going to throw one thing, you can say that.
The other thing is efficiency. And so when you throw an error in Swift, one of the things we put great effort into is making it so that throws in Swift is not like exceptions in Java or C++. Producing errors are actually relatively efficient.
But because that error gets type erased to the
Error protocol, that means that if the value you're throwing is above a certain size, it has to be boxed under the heap. And so you get an allocation for that error that you produce. And for normal code, this is not a problem at all. This is like so much in the noise, it's not even worth considering, but there are classes of code where you care about deterministic performance and things like this, where that's not ideal.
This transcript was recorded as part of Swiftly Speaking. You can watch the full original episode on YouTube, or subscribe to the audio version on Apple Podcasts.
SPONSORED Spend less time managing in-app purchase infrastructure so you can focus on building your app. RevenueCat gives everything you need to easily implement, manage, and analyze in-app purchases and subscriptions without managing servers or writing backend code.
Link copied to your pasteboard.