Matt (00:00): Hey, Ben. Ben (00:00): Hey, Matt. Matt (00:02): So, you know, the other day we were chatting about languages and all the various things that are interesting to us about languages and mm-hmm. one of the languages we mentioned along the way of, in fact, many of the languages we mentioned along the way, we don't really have any idea about. We kind of futzed around, said some things, made some words up. In in particular when we were talking about Carbon, there were some sort of magical properties I ascribed to other languages like witness tables, I think was the magic words I used, which I don't understand. But somebody contacted us and said, "You don't know really that much about the languages you're talking about, do you?" And I'm like, "Well, no." And in particular, Swift, we mentioned it very much in passing. And and Doug Gregor, who is well, who's on the call with us, and we'll speak to it a second, said, "Why don't I come and tell you a little bit about Swift, because clearly you know nothing", which is absolutely true. Ben (01:01): . Matt (01:02): Yeah. So we kind of called out to Twitter saying, you know, Hey, correct us if we're wrong, and someone called us on it, and here we are. So, hi Doug. Doug (01:10): Hello. Glad to be here. Matt (01:12): Very pleased you could come along and yeah, try and divest of some of our, like, thoughts about languages that we don't understand. So yeah, we experienced them very much from afar, and now we've got somebody who can tell us a little bit about it. So you are in fact one of the original designers of the Swift language, so you are best placed to talk about this kind of thing, right? Doug (01:36): Yes. I was one of the first people that worked on Swift back 10 years ago. And I'm on the language working group that guides the direction of the Swift programming language. Matt (01:48): Got it. So there is, I didn't realize it, it had been around that long. Maybe that's just how old I'm now feel, but 10 years. How did it come about that you started working on Swift? What kind of led to Swift and your background feeding into it? Doug (02:00): My pre-Swift background is actually a lot of C++. So I spent good 10 years on the C++ committee, worked on things in library, like std::function some language features like variadic templates, which I hope you've used, Matt (02:14): I have! I'm a bit of a, an old, old school C++ program, but I have used variadic templates, , Doug (02:21): Excellent. And then I was part of the group that implemented C++ support in Clang back when Clang was new and unknown to the world. We were implementing templates and all the major overloading, all the other major features that you need for a full C++ compiler. Matt (02:39): Gosh, gosh, those were indeed heady days. I remember that when GCC was the only pony in town really. And so we were kind of stuck with it unless you were on a Windows platform or anything like that. And then suddenly Klan came along and we, we realized, oh gosh error messages could be so much better, amongst many other things Doug (02:57): They could, There's a lot of benefit you get from being able to start fresh knowing what the, the best out there is and try to improve on things. Matt (03:05): Right? And, and of course Clang was written in C++, whereas GCC at the time at least was all C all the time. And so you had an opportunity to, to build something in the language you were writing for, which must give you some interesting thought processes about how things fit together. Doug (03:23): It certainly helps, you know, you're using what you build every day, all day, right. And you know, you know where the weak points are so you can improve on them. The Matt (03:31): Ultimate dog fooding really. Indeed. so that was C++. And you know, obviously, I mean, I, I know you from like Boost libraries and things, as you say, your committee work and whatever, but how did that lead into Swift? Doug (03:46): Right. So at Apple, which is where Clang started we used primarily C and Objective-C as the language in which we write our frameworks and provide APIs for developers to write applications that, you know, on the Mac and iOS and so on. And Objective-C is essentially like an object oriented layer on top of C very much inspired by small talk, and it's an interesting language, but what we realized is we need something better, Matt (04:16): Right? Doug (04:17): And because there are things about Objective-C that we really liked, and there were problems there. So being on top of C, it was not memory safe. Matt (04:25): Got it Doug (04:25): And there was no way to fix that. Matt (04:27): Right Doug (04:29): And there's many other languages... Matt (04:30): Many languages have tried to fix C's you know, any number of _FORTIFY macros and sanitizers, but, but ultimately it's a fundamental limitation. Doug (04:39): Yes, fundamentally, it's a limitation, and you really have to start over to build memory safety in from the beginning. And we also wanted modern conveniences and make it an easy language for people to, to start learning and write good code in. And so that was the, the genesis of the Swift project, to build a better programming language in which to build apps and, and other programs that was safe, easy to use, but still had the sort of advanced type features that we need to really build great libraries. Matt (05:09): Got it. So is it fair to say that there was a kind of domain specific need for the language? Like, or rather the, the domain, sorry, the language was made to fit a particular purpose to start with, you mentioned applications specifically, which is sort of has a different flavor and feel to it than say just writing backend servers or mobile web pages something, something, you know, it's, was it, was there something specific about the language that was tailored that way or Doug (05:36): Not the language per se: So the language was always designed as a general purpose programming language where you could build high level applications, but also go off and write servers and write more low level things. It was intended to be a scalable language from the start. I would say the first community we went out to to say, "You should really use this because it's so much better for what you're doing", was the application development community. Matt (05:59): Right? And of course, in the position that you were in, you had a set of folks who were already using Objective-C to do this, because that was the easy on-road to making apps in iOS and, and the like. And so this was kind of a drop in replacement, so was, I know we talked about successor languages and you know, it's Carbon being a successor to C++. And, and you know, in the email exchange we had before this, you were like, Well, what is the, what does it mean to be a successor? But was, is Swift the successor to Objective-C by your definition? What, what do you, what is a successor language? Doug (06:29): Right? So you, you had me thinking from your last episode about what it means to be a successor language, and Swift is the successor to Objective-C, but that's not really a statement about the language. That's more a statement about the platform. Matt (06:47): Okay. Doug (06:47): So Objective-C is the language in which Apple was writing its frameworks and building the SDK that you program against, right? This is what the UI frameworks are written in, for example. Right? Ben (06:59): And, and the platform in this context is iOS or, all of Apple's products? Doug (07:03): So it can be anything. Ben (07:04): Or ...yeah. Doug (07:05): So it's, it's all of Apple's products. That's, that's the Apple-centric view, but, we use platforms all the time, and they're written in a programming language. Now, most of those are based on C so pretty much any operating system you use has got a POSIX layer, and the way you interact with your operating system is through those POSIX C headers. So C is kind of the lingua franca of all APIs that are, you know, tied into a platform. It's also unsafe and inflexible . And so one way you can view Swift is the, the successor to Objective-C is we want to use this to build our platform APIs. So it's a better way to interact with the system and we can provide these, these rich APIs that are also easy to use and memory safe. Matt (07:51): Right. So the way that's sort of, I'm, I'm hearing that is that like it's a successor to the the hole that Objective-C would leave if you took it out, right? You were like, we need something to plug in the gap here. And, and in the case where you were, this was the layer directly above the operating system. But it's not like, it's not like I can take Objective-C and just change the extension from ".c"or whatever the other extension is to ".swift" and, and run it. So it's not a successor in as much as I can reuse code, or is it, is that how, how does interoperability work? Doug (08:23): Right. So you certainly cannot just take Objective-C code and compile it in the Swift compiler and it, and it works: instead Swift is a very different language. So it's emphasis is very different. We can talk about that, but it's much more in the realm of generics and value semantics and so on. But it does provide interoperability with Objective-C. Okay. That goes fairly deep. So at the moment, Swift became available, you could take any Objective-C api, any framework, and the compiler would automatically translate everything it had in it, every bit of api, and you can call it directly from Swift. So from day one, Interesting to use everything that was there. Matt (09:02): Right. Doug (09:03): And you could do so at a like, file by file granularity within your project. Matt (09:09): Okay. Okay. Got it. Doug (09:10): And the idea here is when you have this really tight interoperability, so I can write code in Swift that uses Objective-C and vice versa, so directly Matt (09:18): And vice versa. Okay. Bidirectional. Doug (09:20): Okay. Yes. So you can also write "@objc" on some of your types in Swift, and then they become available to your Objective-C code. Matt (09:28): Got it. Doug (09:29): This is a great path forward because it means that you can write your new code in Swift, but leave your old code in whatever language you have in this case Objective-C, Right? And sort of incrementally work toward sort of the safer, more ergonomic language as you rewrite things or as you pick up new features and do do new work. Matt (09:50): So from just froma point of view of what one might consider success of that does seem like an important characteristic is that you can, in order to succeed a language, you have to be able to use something that was there from before. And so it sounds like you've got a story for, for it there. And the interoperability with Objective-C is pretty strong by the sounds of it. Again, as you say, you can do it bidirectionally. That is, I mean, I guess that's what, when we were talking about Carbon and successor of C++, certainly when I was sort of internally and spitballing the idea of like, what does it mean to be a successor? Having very strong interoperability so that you can, you know, so that I can go to my manager and say, Hey, I know we have this, you know, 250,000 line program written in C++, we'd like to try out something new where we haven't got these restrictions. Matt (10:38): Can I rewrite the whole thing? And of course, the answer will be no. And then even when you start a greenfield project, you know, Hey, I'm about to start this thing, I'm probably gonna spend two years on it. Do you mind if I try some random flash in the pan language I've just found on Hacker News, You know, those things are difficult sells, you know, for for good reason, right? Yeah. But in order to be able to sort of leg into it and say, Well, okay, we're gonna make the parser of the, or the new parse for the "blah" part of my project, I'm gonna write that in a, in a new language. And then you have a, a way to incrementally improve upon it is a successor. But that's sort of like I guess that's a sort of logical successor in terms of like, we replaced the niche that was there before. There's not like a spiritual successor aspect to this then. I mean, and, and maybe there isn't in Carbon either. I mean, there's not, we don't know enough about Carbon to be able to answer the, those questions. You know, some of the things are maybe a little more spiritual, but it sounds like in order to fix some of the things that you had in Objective-C you mentioned memory safety. Presumably you have to change the way you think about things. Doug (11:36): Yes, you do. And I mean, in many ways Swift is very different from Objective-C. Objective-C is object oriented through and through. You build, you create a class for anything that you want to do, Swift goes the opposite direction in a sense. We like to use value semantics throughout. Now, semantics are something that are very familiar to C++ programmers, right? Yes? Matt (11:59): We miss them whenever we have to use any other languages. And we forget that everything else considers, everything's a reference. And then we're like, why is it changing under my feet? Yeah. But value semantics are very powerful. Doug (12:08): Right? And so they're very, very, they're, they're core to much of what Swift does. So it, we made it very, very easy to define new structs and new enums and compose those together. Value semantics are very nicely composable. We skew toward immutable or at least make immutable things as easy as possible. So the way you declare a variable usually is "let" it's immutable. If it's, if it's value semantic, right? It can't change Matt (12:33): A sort of LISP-like 'let", Doug (12:36): Now if you want mutability, you can use "var", and then you have local mutability to just change that particular variable. So it's value semantics through and through. And that also goes for the standard library. So all the, the basic types like arrays and dictionaries and so on. And also string, these are value semantic types backed by copy on write? It's copy on write. Matt (12:57): Oh, I was gonna say, so like, yeah, dictionaries, for example. You, you don't, you take a dictionary, you just get a new dictionary back by saying, you know, you get dictionary and add this key and value to it, and you get a new one. But, you know, that's suddenly when, if people have have waxed lyrical about immutability and languages and stuff, that's the way they explain it to me. And I say, That's all great and wonderful and everything, that's sort of the logical view. But under the hood somewhere, someone's doing something like copy on write for you. And that had to be written using mutability, but you're presumably hiding that away. How so? Doug (13:24): So we are hiding that away. So the idea with Swift is we do have mutability. If you have a variable of dictionary type, you can go add and remove keys from it, and that's perfectly fine. All right. Okay. Under the hood, it's doing copy on write as an optimization. So if you uniquely hold on to the thing that you're referencing, we'll just modify it in place and there's no problem, Matt (13:42): Nice. Doug (13:43): But if you've made copies elsewhere, well then you have to go and make a copy for yourself to mutate locally, and then that's yours to keep, and you can mutate it efficiently locally. Ben (13:53): So, I mean, there's lots of benefits to mutability and, and programming for sure. Are there specific benefits for thread safety that you get for that? And I, I ask this because I've, I've written a fair amount of closure in my life, and one of the wonderful things about closure is it's actually kind of hard to write thread, unsafe code, like you have to try. And that's really fantastic, right? Like you sort of like wonder, you know, wind up in these situations where it's like, Oh, I have this map over this function. Well, I'm just gonna change that to pmap and now it's just gonna run in threads and my life is nice now. So do you see that in Swift because of this sort of, you know, default to immutability? Doug (14:30): Yes, we do. So immutability is very good for, for concurrency there. Value semantics are also good. As long as you're making copies, you're perfectly fine because your, your mutations stay local, where you have a local variable and you are just copying across the wire. So we did recently introduce a concurrency model into the Swift language. So there's things like, async/await, we pulled in the notion of actors and we have current tasks running. And when you are working in this realm, we are using primarily value types or these actors, which is a kind of synchronized reference type. You don't have to worry about the synchronization problems because you're always working in different values. This is actually something we're working towards in the Swift language. Over time, we're going to start enforcing in the compiler that the types that you share across your concurrency boundaries are actually these value type, like things that are safe to share. Yeah, Ben (15:26): That was gonna be my next question is can the compiler check that for you? Be like, Oh, you're passing in the variable here and that means that this won't be thread safe, which means this other thing won't be thread safe. So no . Doug (15:36): Yes. And that is what we're working toward. So we call it data-race freedom is one of the big pushes of, you know, Swift 6 in the future that we're working toward. Now, you can enable some of this checking already in the compiler where we will check when you send something from, you know, one concurrency domain, like a concurrent task to another one. Is this something that is safe to send? We call it Sendable. And if it's a value type composed of other Sendable things, yeah, of course it's safe to send, It's just a copy. And if it's a reference type, like a class that doesn't have any synchronization, the answer is no. You need to go find another way to do this safely, because you probably have a data race. Mm-Hmm. Matt (16:13): . Cool. That's pretty cool. I mean, so how, how does the, I mean, I'm, I'm always have a sort of performance focused view of the world through far too many years of staring at assembly code for obvious reasons. How does the performance of Swift marry up or compare against some of its, you know, contemporaries, I guess C++, You know, if we, if we're like maybe building an argument that Swift might be a viable successful language to C++, then one of the niches that you have for C++ is like an embedded machine with no resources or a huge cloud running, millions of copies and all that kind of stuff. So how does it compare? Right. Doug (16:51): So Swift is a, like fully compiled language, and so it is generating machine code. There's a pretty powerful optimizer that understands the copy on write semantics. It understands the reference counting mechanism, Interesting mechanism behind the scene. And it does a fairly good job in most cases. Now, one of the things you'll notice with Swift, if you're coming from C++ is you have a little less control because, because of the memory safety, because we're doing reference counting to keep that memory safety, the compiler is doing some of this work behind the scenes, doing a lot of work to optimize it away. But you can fall off a cliff sometimes and right, Matt (17:28): Sometimes you'll do something and it's like, now I can't prove this. I have to count it at run time or whatever. And that might be a surprise to you, just like when, yeah, some optimizations you otherwise rely on C++, like in aggressive inlining you hit some threshold and suddenly what should be just return three is a huge block of code. So this exists in other languages, these kinds of edges. Doug (17:48): This exists in almost any language that has powerful abstractions where at some point you've abstracted so far, Matt (17:54): I was promised free abstractions, I want my money back... Doug (17:59): Yeah. So in Swift, we are, we are working toward an ownership model that will surface in the language they can help here. So if you see a case where the optimizer hasn't done exactly what you want and you want to take more control and say, No, no, don't make a copy here. I want you to move this data down and make sure that this data has moved so there's no more copies, no more allocations, then you can do that. And so we view it as most of the time the optimizer does a good enough job, you don't have to think about it and it's easy to work in this model. Sometimes you hit a hotspot and we'll give you the power tools to go ahead and make that better without losing any of the safety guarantees, Matt (18:35): Without losing..So that's the thing I was gonna ask, because this sounds a little bit like, oh my gosh, I'm gonna have to roll my sleeves up in, in, in Rust or whatever and type "unsafe", and then I'm, then suddenly it is the Wild West again, Doug (18:46): . So yeah, there are unsafe facilities in Swift as any, you know, any, any safe language always has unsafe facilities somewhere and you can use them in Swift for this. What we're working on is a stronger ownership model so that you have something that's safe, but still gives you a lot of control for these cases. Matt (19:03): Interesting. So could I write a device driver in Swift: that's like usually the edge case where, you know, the even venerable C program has finally justify the volatile keyword as being an actual "No, this is what it's meant for". Doug (19:15): You could certainly write a device driver and Swift you'll have to play around a little bit to get the memory mapped IO kinds of interactions, right. And you will certainly be in the unsafe code territory, but yes, you can write a device driver and Swift you'll probably want to limit your usage of features like generics that are separately compiled in a different shared library. Matt (19:38): Right. But I mean, you would do, if you're writing in C++, most of the time you will have like the bit that's actually touching the, the memory mapped IO and then you're very quickly, you add your own abstraction. So that's not necessarily a deal breaker there, but so you mentioned reference counting and I meant, I'm sorry, jumping all over the shop here. Anything that has reference counting I think, you know, Python, Python has reference counts and they're great right up until you get a cycle and then you're like, Oh dear, now we need some other Deus ex Machina thing to come in and save me aka garbage collector. Does Swift have a garbage collector? Does it, how does it avoid having one if if, if it doesn't? Doug (20:16): Right, so Swift does not have a garbage collector. It is reference counting and if you have a cycle, you've created a cycle in your data structure somewhere and you should break the data that cycle in your data structure. So there are tools to visualize them when this happens, to see where the links are, and then you would go in and replace a strong reference to something which is the default with a weak reference. Right. Doug (20:42): And so you have a weak reference here that doesn't keep the object alive, however it knows when the object goes away. And so weak references in Swift are actually tied in with optional types, which is part of the language. And so if you have a weak reference to, you know, a person object, which you really have is you have an optional person and that will refer to the person if it's still alive and it will revert to the nil state, which means there's nothing here when the object is gone. And so it's a safe way to break up your cycles. Matt (21:13): Got it. So like my classic example if for thinking about this is like a doubly linked circular list that you might have with a magic head pointer or whatever. And so the classically there you'll have the next pointer is the strong one and the prev pointer is like the weak one or something like this to sort of say, well, okay, this is a cycle, but what kind of overhead is this? Because you know, there are many ways to, to do that sort of tracking how might the work, you know, I'm used to seeing like, well there'd be 16 bytes for everything. There's eight bytes one way, eight bytes pointed back the other way. But clearly if there's something else going on, there may be a reference count somewhere and there may be some other data structures that are allowing you to determine when the last reference goes and like maybe nulling out a load of stuff or how, how does that work? Just humour me... Doug (21:52): Sure. so you know, reference types are the things that are actually reference counted. And so reference types already have an object header. I see that is a common layout amongst all the types. This has things like type information. So you can dynamically ask the type of an object, what are you, this is used for things like dynamic casting C++ has the same notion, right? Matt (22:11): Right. Doug (22:11): In that object header is a reference count with information about you know, what the current reference count is, whether there are weak references to it and got it. It's all, Matt (22:22): So there's sort of a shared pointer-like thing going on, but it's actually in the object header. It doesn't have to be a separate block again, although, you know, most of the time you can combine them in C++. But like there you, because you you and you know that every object is d has this header, you can put the information there Doug (22:38): Exactly. And it's always in the same place for all objects. So we can reason about objects abstractly and because it's one set of operations that are quite critical for performance, you know, we've, we've optimized it with atomic operations throughout to make this as cheap as possible to do reference counting. Matt (22:54): Got it. Okay. That makes a lot of sense to me. I mean I've recently, I've been spending a lot of time in the Python world and I've been watching and reading how how they're counting and tracking and garbage collection stuff works. And so there's been some interesting thoughts there. I sorry, apropos of nothing. This is just more me going "I found this the other day, it seemed really cool". There's a thing coming for Python soon where they're actually gonna have only use a small number of bits to count the references and then it saturates and once it hits the maximum they're like, okay , there are just too many to track now it has to be garbage collected later on, which I thought was an interesting trade off, right? If you think most of your objects have one, two, maybe three references to them, you don't wanna store a whole 32 bits or 64 bits. So like that's why, the reason I bring that up is because like what is the minimum size of an object? Obviously you need to have a weak count and a strong count, which means at some point you've made a call of like what's the maximum number of incoming counts gonna be? Is it 32 bit, is it 24? Are you packing these things or whatever? Doug (23:46): So I don't remember the exact bit counts. I'm very sorry I don't have the information. However, when we saturate we will do a side allocation to track very, very large reference counts so we can deal Matt (23:57): Oh I see. That's cunning because Doug (23:58): We can't just stop, we can't stop and fall back to a garbage collector because there is none and we can't just say this, you've hit some arbitrary limit now your object's never gonna go away. That would be utterly unreasonable for the programmers Matt (24:09): Immortal at 65,000 references or whatever. Yeah, Doug (24:14): Yes. That, that does not work in practice. Ben (24:15): We got a ton of these things we should probably just stop tracking them... Doug (24:17): . Well we can't because part of having reference counting system without a garbage collector is you get deterministic destruction Matt (24:25): Ah, Doug (24:25): There, which is an important property for understanding how your program is gonna work. Right. Matt (24:31): That is a very interesting point there. Now obviously having reference counts means that you say deterministic, it's deterministic when the last reference goes away. But you know, in that, in the sort of standard way that when you have reference counting, it's like when everybody owns it, nobody really owns it. How, you know, are there ways of dealing with this, you know, things like files you know, canonically and C++ you have like a file object that opens the file then you know, the destructor will close the file and it's like, well if I lend out references to other people by file ref, that's on me. And when the object goes away, that's tough. But in things like Python, which have both, you kind of have to use these with blocks to sort of magically say, well actually there's a scope that's not strictly tied with the lifetime of this object. Is there a Swift equivalent of that? Doug (25:14): The Swift language doesn't have a specific equivalent to that. So this will often be handled by reference counting. Sometimes you'll just write a defer block if you really want to just close it at the end, no matter what. You can write a defer block to do that. I do think this will change in the future. So one of the pieces of the ownership work that we are doing in the Swift language involves the introduction of move only types, Matt (25:40): Right? Doug (25:41): A move only type: Okay, you would actually, you're controlling the lifetime, you know where it is created, you know where it's gonna go away and it's explicit if you're handing it off to someone else for them to actually close it. Matt (25:51): It, is it fair to say a move only type is effectively something which can only have a reference count of one while it's alive and then it's dead after that. So it's just got like a reference count of one and then you can make these assumptions because if you, if you have one then you are the one who's gonna be taking the reference down to zero when, when the time comes. Doug (26:10): Yes, you can think of it that way, but having a ref count of one implies that there's an object header that's storing that reference count, which it doesn't see, right? This is all handled by the compiler, it's statically known where this, this move only value as this value of move-only type is going to be destroyed. Matt (26:27): So if, forgive me for bringing C++ back into this again from this. And so like every object currently is sort of a compiler assisted shared pointer. So every reference, every reference type a referenceable type is a sort of compiler enhanced shared pointer where you know some things about the semantics and you can do some optimizations and stuff. And then these moveable types would be more like a unique point of where you're like, well I can make a reference to this and I can hand it around to people. It doesn't live on the stack because it can't because I made my stack's gonna go away, but I can pass it on to somebody else as long as the compiler can prove that I can't see it anymore after I've moved it. Is is that a fair thing or am I, am I Doug (27:03): I think that's a fair analogy. Although except for the part about the stack, there's no reason that you can't put a move only value on the stack because you know when it's ownership is being transported around and it could be returned and that's perfectly fine. So I think think, I think yeah what this is is, you know, with unique pointer it has to be on the heat because it's allocated with new Matt (27:25): Yeah, maybe there's some Clang passes nowadays that do some very, very clever analysis of like memory elision and all that kind of stuff. But most of the time you're just guaranteeing that you've put something on the stack, Doug (27:35): Right? So Swift has a deeper understanding of what's going on with it types declared on the stack. And so it will define it on the stack if it's needed and it can move it back to the caller. If it needs to move the value out, it's perfectly fine. The address does not to be need to be stable. Like with the unique pointer, you're expecting that the address of the the value is going to always be stable and well then of course it has to be on the heap with a value. In the Swift world, the address is not necessarily a stable thing. You can't reason about it from within the language except in very narrow circumstances where you've told the language, I need the address of this thing and you're not allowed to escape that address out to, to someplace else. Matt (28:14): Got it. Okay. That makes a lot of sense. So it sounds like it is as performant as you would expect from a language that's compiled. There isn't any overheads other than the reference counting that if you wanted the semantics that the reference countable objects give you, you would have to have somewhere or another either through garbage collection or through your own reference counting, like a shared pointer would give you, but you get some acceleration from the compiler in this case. That's, So how does memory safety fit into this? Is is that because of these reference counts that the memory safety guarantees come about and and the fact that objects can't die? I mean how about things like an array of things and not going off the end of arrays? That kind of aspect of memory safety, Doug (28:51): Right. There are certainly other aspects of memory safety and so yeah, in addition to, you know, reference counting being the memory management strategy and not escaping pointers out or not even showing pointers into the, the user model. All arrays and accesses are bound checked. So this also includes things like if you have an optional value, you need to be careful about trying to get the value inside there and deal with the case there might not be a value there, Matt (29:16): Right? There's no kind of "->", trust me, I know it's there. Just interpret the bits there that an optional has everything is ".at()" of the thing or a ".get()" or a dot whatever the thing that throws, I forget which one it is now, but like, it's like, no, we're done here. Right? And then what happens actually, what is the, yeah, what happens if you, if you do access an optional that isn't there Doug (29:35): For sure. So the language makes it harder for you. So there's a lot of syntactic sugar around, I have a value of optional type and I want to be able to do something on it, but only if there's a value in there. So there's some nice, okay, there's this nice if let syntax that says, hey, if there's a value inside this optional, get me the internal value. And that's what's in scope inside the body of the if statement and outside of it you can't do, you can't reason about it otherwise outside you have to reason about the optionality Matt (30:04): Bit like that. So like the two phase if statements that I would use in options in C++ said except it's mandated. You know, like where I would normally say if the thing and the thing is not empty, you know, if auto the thing inside of it and the thing is not empty, then I've got got the actual object now in fact, no, you can't do C++. Yeah. Forget that I said that, but yeah, I see what you're getting at... Doug (30:22): Yeah C++ doesn't actually let you do this. You, you can check is there a value in here, but then you always still have to write, you know, a star beforehand to say, I know there's a value in here. Matt (30:31): Exactly, which is what I was thinking there. But of course, yeah, I in this instance you're saying no, I want you to do two things. Either it's not there and which case don't do anything or it is definitely there and give me the thing that it's referencing in one go. Got it. Yeah, Doug (30:42): That's exactly, and there's, there's some other nice sugar around it. So say you have a, a value of optional type and you want to call a method on it, but only if there's something there. So you can do this directly with saying, you know, "x?." And then call the function And what the saying is, well if there's something there, go ahead and call the function. If there's nothing there, you can't call the function. And then the whole result of that call is then wrapped up an optional that tells you did the function call happen And if so, you get the value in there and if the function call didn't happen, then you just get deal. So it makes it very easy to work with optional in a way that's correct. Now you can force it there is a force expression, it's spelled with a postfix exclamation point, Doug (31:28): Big exclamation point. Be careful here and that will do a runtime check to make sure that there is of value in there. And if there's not a value in there, it'll give you a nice error message that says this is the mistake you made. Here's the, the point in your code at which you did at the debugger will jump there. And so on many programmers that use Swift just ban these things outright. They say don't use the post exclamation point. My view is a little bit softer on that. I think if your program invariants are such that there must be a value here and there's no other reasonable way to write the code, it's fine to use it and it's documentation that this isn't an invariant, Matt (32:06): Right? Right. Yeah. You're not expecting it to be not there, but for whatever reason you took an optional as a parameter or whatever, it's an optional field in a class or whatever. But you said like, I've just checked the enum value that says this is of some particular type and I know that this must have a price or this must have a quantity or must have a whatever. And so as you say it's documentary, you're like, no, it's actually a... this is an assertion here, it's an assertion saying that something else is wrong. Right. So talking of errors that's another sort of like place where often languages can have quite strong opinions. C++ sort of has a split brain about whether exceptions are great or at least the community has a split brain about whether or exceptions as they're written are are the way forward. How does Swift handle unusual? I mean I won't even say exceptional say situations because sometimes or not, but how does it deal with the equivalent of exceptions, errors and the like? Doug (32:56): Right. So errors are, they're part of the language and you can mark a particular function as being throwing. So if you say it throws, then it can produce errors. So in that sense it's like C++ in that you have functions that can throw and functions that can't throw, but the default is flipped. So most code and Swift cannot produce errors, it just does its job and produces a result value. Matt (33:19): Are you perhaps intimating that C++ has a wrong default that seems very un... unlikely. Doug (33:24): I would never say such a thing, but you can infer what you wish. Matt (33:28): Okay. All right. I don't wanna put words in your mouth. . Doug (33:31): So you can opt in to my function can throw when you do, so of course the caller must handle the thrown error and so you can do this with a do-catch block. You can do this with also making your function throws. There's a couple of other options you have there, but one of the things that Swift did that's a little bit interesting is we mark all of the places in your code at which you do something that could throw. So okay, if you're in a function and you call some, you know, open a file function that could throw an error because it wasn't available in the file system where you have that open call, you mark it with the try keyword and it says, here is a point at which there might be an error thrown and it needs to be handled somewhere else, but it marks in your program logic the point at which you have to think, wait a minute, this is a point where I'm gonna get some possibly interesting control flow that jumps out at my function or jumps out to a do-catch block. Ben (34:32): That sounds an awful lot like a Java checked exception. Am I, am I misunderstanding that? Doug (34:37): I think you are misunderstanding that. So with the Java checked like exception, I don't think you, you don't mark it in the source code of where it actually happens. So you, you say in the function you say here are the, here are the exceptions that I might throw Ben (34:48): Mm-Hmm Doug (34:50): And then the compiler does a check that a function that you call inside there doesn't produce an exception that isn't covered by that set of exceptions. But you don't write anything in the source to say this is the call that might throw a particular error Ben (35:05): Fair. Matt (35:06): So I think what you're saying Doug, is something like you'd say "let f = try open_file(blah)". So you have to put this try keyword actually in the expression that could cause an exception to be thrown every time that you call a function or at any point in where you could, where an exception could be thrown, you have to kind of tag it in this way, which was like a huge criticism of C++. Is that like effectively it's an interview question, where are the points where an exception could be thrown in this innocuous looking piece of code and it's like, well everywhere a hundred times, right? And so is that the, is that what, what you are doing in that instance? So as well as marking at the function level, I am a function that could throw and if I'm calling a function that could throw, I have to mark my function upwards as like, well I also could throw unless I'm handling it in this instance, even when you call something within that function that could cause an exception to happen, you have to sort of tag it with a like, no this, this part of the expression could throw that. Matt (36:01): Right. Am I right there? Doug (36:02): That's correct. And the reason is you can think about this like, like a return, right? What does return tell you? It tells you at this point my function's gonna stop and execution's gonna continue for my caller. Try is saying at this point, instead of going on the next line of code, it's gonna jump outta this code block and maybe you'll end up in a do-catch, a catch block somewhere. Maybe it's a throws function and the the errors gonna propagate out. But this control flow, especially in the error case, which is the hardest one to think about when you're writing code, it's a reminder that at this point you've got some control flow out. So you should think if you've tried to start setting up an invariant but it's not in a good place, if this fails well then you better put a do-catch block around it or rework your invariant in some way that makes sure that you're okay along this, along this path that might jump out the function or this bit of code. Matt (36:53): That, I mean from my point of view, I think I, having spent so much time realizing that the exception gonna be thrown a weird places and that that matters in C++. I can understand why putting that in is there but maybe you know, in the garbage collected languages like Java, it doesn't matter "as much" because you know, some of these invariants that you are are handled by the garbage collector later on it's like well I don't care if I made an new object, it's gonna go away in the end of the time. Doug (37:17): Yeah, I think there's probably one other thing going on here, which is that thrown errors are fairly rare in Swift. So if you think of C++ bad_alloc can happen to you anywhere at any time and it makes it impossible to say which calls are going to possibly throw that exception. In Java you have the NullPointerException, these notions don't exist in Swift and so there aren't systemic problems that can happen to basically any line of code anywhere. It's someone deliberately made this thing such that it can throw an error because there's an underlying reason for it. And so the cases where you actually need to mark something as try aren't all that common, which keeps them meaningful because they're rare. It's, you know, it's something you need to think about, Matt (38:05): Right? It's not like every third line of your code has to be trying something, which it would be if it was like say the equivalent in C++ or indeed other languages that like Python that use exceptions for control flow in some cases, right? Yes. You know, so that makes sense. So is there a sort of more standard way of, you know, file opening is a classic one because you know, sometimes opening a file is exceptional. It's like, well I know the files there so it's an exception if it's not there. But other times it's like I'm opening the file cause I'm not sure if it's there or not and I don't wanna do a round trip and have the race condition of what if it's not there afterwards, right? So it means different things to different people. How, how does Swift sort of communicate the non-exceptional failure type? Is that optional? Is that something else? So Doug (38:44): Often it's optional, so I'm not sure that I would do it for opening a file because you probably wanna provide.. Matt (38:49): You wanna know why it wasn't there, right? Doug (38:50): You want more information about what happened, but often it's, you know, either I can do this or there's nothing there and optional is used for that. So it's fairly common to, to have an optional a function return and optional if it can't do something, if there's no value there already. And the fact that optional is sort of syntactically sugared throughout the language makes that very easy to work with Matt (39:12): A convenient thing to do. Right. Got it. That makes sense. Well, as I keep asking C++-centric questions, I feel poor Ben is left sort of out, so I'm gonna throw, throw him a very characteristic bone and he's gonna hate me for this, but like, do you have like is there like a an appropriate sorry, a very Swift-specific test testing sort of framework. A lot of languages have the newer languages like Rust, whatever will have like a way that you do testing inside the languages, like it's bound up. There's like cargo test is a thing you could just do, you write code in a particular way with a magical thing and this is the test for that bit of code and, and that's great. How does testing fit into, into Swift? Doug (39:49): Sure. So most testing goes through the XC test library. So this is something that's, that's been around for a while. It's actually inherited from Objective-C, but I think it's sort of the spiritual lineage goes back to CPP unit and Junit for the, the style of test those. But yeah, essentially you write separate test targets and you just write a bunch of test functions they have set up and, and tear down and can provide assertions that, you know, values match. There's a fairly rich API there for writing tests and it tends to be integrated in, in the ID fairly well. So it's easy to go and, you know, rerun one given test or run your whole test suite and see what, what, what happened Ben (40:28): There. So one of the unfortunate things that that happens whenever you're building a platform for somebody else to use, whether it's you know, a standard library for a programming language or just some other, you know, library within a greater ecosystem is that you can wind up with these kinds of platforms and libraries that are n not designed to be tested. So like as a person writing application code, it's very difficult for me to write my test because of the way that you designed your library and not for any particular essential reason, just because it didn't happen that way. So is that something that you're thinking about as you design some of the, the libraries around Swift and the sort of standard libraries you think about like how would a person who's using this test their own code in a way that doesn't require them to like, oh, spin up a database in order to make a function call? Doug (41:16): Oh, that's interesting. So with the sort of, I tend to stay on the lower level standard library and compiler side of things where the functionality tends to be very narrow inputs and outputs and very functional in nature. So, this concern doesn't come up so much. But for folks that are working in like the application space they often have been using Swift abstraction features around protocols to allow for things like mocking. So instead of using a specific, you know, structure class somewhere, they will write a protocol that describes sort of its essential characteristics and then in production they put in the real end implementation with the server backend or the database. And then in their testing they can drop in a different implementation of that protocol that that works on, you know, their internal, local database or some testing data that's, you know, easily predicted and understood. Ben (42:15): Yeah, I mean this can absolutely happen with like very low level APIs. Like I just wanna write to a file, but I don't actually wanna write to a file because if I actually write to a file, then my test runner's gonna clutter my machine with a bunch of temporary files that I don't need. So I wanna pass in a fake file, mock file perhaps or some other thing that I do. But if the API doesn't let me do that, it's like, no, no, no, this has to be an absolutely real file, otherwise you can't do it. Then I'm stuck writing to the file system and then running, writing a bunch of other codes that's gonna clean up the temporary file that I wrote. And then what happens if something in that fails and then I get, so this this weird state. So I mean is that something that you have have considered at all with some of this stuff? Doug (42:52): Yeah, I mean generally when I work with something like that, I would try to have the virtual, the file system virtualized so that we can work on it. Generally it's a good idea anyway with something like a file system because you will be dealing with different file systems at some point in your life. It's not all gonna be just whatever the deposits APIs give you. And so in general, I would say that the way you would write that is you would write it abstracted over a file system. Here is the implementation of my file opening and writing some data and closing it. Generally in Swift you would do this with probably a form of generic programming. You can write a lot of things in terms of protocols and generic algorithms that operate on the protocols for things that can be higher level and then your particular implementations we'll have to target something like a file system if an actual file system and maybe you build a sandbox version for your testing. Ben (43:41): Okay. One other question I had not related to testing. And this is maybe looping back to the thing that sort of spawned this originally is, oh, like, you know, we've been talking for, for a while now about like all these really fantastic features in Swift and if I was an Objective-C programmer, which I am absolutely not, but if I was, I can imagine that I would really be interested in taking my giant legacy Objective-C code base and starting to introduce Swift and you sort of had like a great story for that in terms of like being able to, you know, mix and match the, the source from, from both of them and operate cleanly. Do you have a similar story for C++ either today or like as you look forward into the future? Like if I'm, if I have a C++ code base and I wanted to introduce Swift into it, what does that look like today and what do you want it to look like? Doug (44:29): Right, So today what it looks like is you would probably wrap up your C++ interface in a C interface and we can access that directly. However we are actively working on C++ interoperability for Swift. So there, there is a work group that's active on the Swift forums. It's, it's part of our general like open evolution process that is working on Swift C++ interoperability and they're working through, you know, what does it mean to map a C++ class or method or template over, Matt (45:00): Oh, template, right? Doug (45:02): Template's always exciting over into Swift and use that from the Swift language and is it ergonomic to do so? Do many C++ APIs come over well enough that you can go ahead and use them directly from Swift, or at the very least write some clean little wrappers that make it easy to work with those APIs and again, with the other direction. So have your Swift library and export from it, a C++ interface that you can go and use in the C++ side of your code. And the vision here is to do basically the same thing for C++ that we ended up doing for Objective-C, where you have this tight integration that makes it easy to do sort of file by file adoption of Swift within an existing C++ code base. Matt (45:45): That's, that's a lofty but impressive goal. I mean that, that's kind of what was the USP for me with Carbon amongst other things was this ability, the, this intention stated intention. Obviously Carbon is a lot of things that not, it's not a lot of things at the moment, it's a lot of ideas. But to have that very strong intreop, you know, the ability to instantiate a C++ template inside another language and be able to code gen and do all the, the, the hairy things that need to go with that is kind of an appeal when you are trying to move a code base from one to the other. But if your planning is for Swift as well, that opens up the door and definitely means that Swift is, is is at least as good success into C++ as the mostly vaporware Carbon at the moment. So that's, that's an impressive... Doug (46:30): We hope to make it, you know, nice and ergonomic. It's been really interesting. We've focused a lot on containers, right? You have a container like a vector or your own container. We wanna bring that into the collection model and let you use Swift's for-each loop on it and use all Swift's collection algorithms on it. And it sounds like a small thing, but you know, the C++ iterator model doesn't exactly match the way that we, we iterate through things in Swift. And so the, the C++ interoperating work group is tackling all these problems, figuring out how to map things, and then testing it by writing new code to see how it works. Matt (47:05): How, how does what even think about doing ownership in those worlds where, you know, you've already described to me if you're gonna have a reference to a something in, in Swift, then you've got this object header that's gonna be there, which clearly isn't there on a vector of ins or whatever. So that sounds like a really difficult and challenging problem to solve. Doug (47:25): It is. So there's a couple of solutions. So C++ is interesting because we, it has some value types and it has some more reference types and the value types, you know, map over this with value types and those are fairly easy. They're, you're dealing with values and so on. With the reference types, it's interesting because we have to know which things they are and you don't have a normal reference counting scheme mm-hmm. . So we have to do something that's maintains the model of, it's a reference type, but ensures that either we can handle it safely. So if they do have a reference counting scheme, we can tie into it and use it. We've done that in some narrow cases. If they don't have a reference counting scheme, that's okay, but maybe you have to refer to this through, you know, code that is unsafe when you're dealing with, with this thing with that, we can't control the lifetime. We don't know the lifetime because the pointer has escaped and it's a C++ type without any...ref counts... Matt (48:20): I dunno if you've ever looked at like how pybind11 works, which is this template library for binding bidirectionally, Python and C++. And it kind of does a lot of stuff behind the scenes where it's like, well Python, obviously everything's reference counted and it can, it can expose a C++ reference counted object as well. Okay. This is, this is in the same thing. If the reference goes up and down, I can add re remove ref or whatever, but sometimes it has to put these into posed objects that are like, well this is my facade onto the real thing and it's kind of an option of that. And if I think the other thing's gone away in C++ land, then I'm afraid it's not here anymore. Although you can still count how many references this object has, it's just unfortunate that it doesn't point at anything anymore. These are difficult problems. I mean it's I'm really interested in in, in how this is gonna pan out, but it does, does genuinely sound like C++ has another potential contender in the arena for, for successor. Doug (49:14): Well if you're really, really interested the C++ interoperability work group for Swift, it's, it's open. We are on https://forums.swift.org/ where they're doing their discussions and I think they have a roadmap document to lay out. These are all of the problems that we're looking at tackling in the rough order in which we're doing it to make this C++ and Swift I interoperability layer work and we're using it now. So we actually use it in the compiler so that we can write some of our compiler and Swift and some of it in C++ and really try out these ideas and see how well they work. Oh, Matt (49:48): Neat. Yes. So you are dog fooding it. So I think I was saw, I happened to notice that you, one of the more recent changes to Swift was to like have a parser for Swift written in Swift and so you're starting to like migrate and share C++ and Swift even in the Swift compiler itself. Doug (50:04): Yes and we have some optimizer passes that are also written in Swift and use this C++ interoperability building mechanism. It's kind of our playground so we can go improve people plus interoperability, see how much nicer the code gets and then, you know, keep dog fooding and iterating to make it better. Matt (50:19): Nice. Well we're running a low on time, but I do want to ask one thing because I did throw a statement that I had just copied off of somebody on, on Carbon in being in, in conversations with them, which was, what is a witness table and , can you explain it to me in two sentences? Doug (50:32): That is tough. So a witness table is the runtime representation that says how a type you have, like an array of integers, satisfies the requirements of a protocol like Collection. Witness tables are used for separately compiled generics and Swift, Matt (50:55): Right, Right. Doug (50:57): Yep. And they're also used for what we usually call type erasure in C++ where you, that you know that you have something used as a collection of integers but you don't know what the type is becuase it could change at run time. Witness tables also have a big impact there. Matt (51:12): So it, in my mind's eye when I, we were talking about it in the Carbon world and whatever, that was kind of what I was thinking is this sort of like little bundle of like, well this is the, the layer that, that allows me to adapt something to fit a, like a essentially a virtual function table but not explicitly defined in the class, but separate so that some other piece of code can patch them together, such as when I'm writing a type by myself, I have to unfortunately go through and manually write all of the protocol myself and then adapt it to a template type or whatever to sort of say, Hey, this is one of those. Doug (51:41): And the virtual table analogy is a very good one because it's the same idea but separated from the object. And again, we also use them for our generic system because we have separately compiled generics. And so you have to pass the witness tables through to say, what does this type actually conform to the protocol? Matt (51:59): Oh, you see, this is making me think more about other cool questions that I wanna ask you, but we, we honestly don't have time, which is really unfortunate. But it does sound like we've got a a, a viable C++ successor in Swift. Certainly when the interrupts story improves with C++. It sounds like it's a great choice for almost everything that you would use for C++, in fact not almost anything you would write in C++ Swift. Is there like a, a single single phrase, unique selling point for Swift that you would like to sort of proselytize or give us, leave us with? Ben (52:34): Hard to summarize that. Doug (52:36): So... Ben (52:37): That's the toughest question. Yeah. Matt (52:38): Oh, I put you on the spot there. I realize I should have said, asked this before . Doug (52:42): It is, what is the, what is the two sentence elevator pitch for Swift? So I think Swift is there to be programming fun and easy and help you build really good code from the start and support you in doing that. Ben (52:55): Cool Matt (52:55): Sounds good to me. Fantastic. Well thank you so much for your time and for taking up us on our challenge of like telling us where we are wrong and we were very wrong about Swift , it turns out. Ben (53:07): Yes. I didn't even know enough to be wrong. Matt (53:10): . So thank you once again Doug for, for coming in and talking to us today. Ben (53:16): Yeah, this is great. Doug (53:17): Thank you for having me. It's great talking with you. Ben (53:19): Thanks a lot.