CppCast - Swift for C++ Developers

Episode Date: July 27, 2024

Doug Gregor joins Phil and Kevin Carpenter. Doug talks to us about his work on Swift at Apple, what the language is like and how it can interoperate with C++. News "Memory Safety in C++ vs Ru...st vs Zig" - B Shyam Sundar C++ under the Sea workshops announced mp-units 2.2.0 released Links "Swift for C++ Practioners" - first in blog series from Doug Gregor Episode 341, with Dave Abraham talking about Swift/ C++ interop "Start with a Protocol" - blog post from Rob Napier (but link to Dave Abraham's "Crusty" talk no longer works) "Option(al) is not a Failure" - Phil's talk about Swift Error Handling "Option(al) is not a Failure" (yes, same name) - Phil's talk about past, present and possible future C++ error handling "Swift Concurrency"

Transcript
Discussion (0)
Starting point is 00:00:00 Episode 387 of CppCast, with guest Doug Greger, recorded 16th July 2024. In this episode, we compare memory safety in C++, Rust, and Zig. Look at the new conference workshops that have just been announced. And talk about the latest release of NP Units. Then we're joined by Doug Greger. Doug talks to us about his work on the Swift programming language. Welcome to episode 387 of CppCast, the first podcast for C++ developers by C++ developers. I'm your host, Phil Nash, joined by my all-new guest co-host, Kevin Carpenter. Kevin, how are you doing today? Doing good. Just recovering from CPP on C.
Starting point is 00:01:07 Ah, yeah, sorry about that. I take the blame entirely. So welcome to the show as a guest co-host. As you know, Timur is still away for a while. And I thought I'd bring you on this time, Kevin, because you're also with me at SwiftCraft. We mentioned that on the show before around a couple of months ago. So you've got at least some exposure to Swift. And we're going to talk a bit about Swift today. So I'm going to rely on you to ask all the intelligent questions. I hope that's going to be okay. After my workshop in Vapor, I'm getting my feet wet, that is for sure. Right. So at the top of every episode, we like to read a piece of feedback. And this one was actually relating to a couple of episodes back.
Starting point is 00:01:52 We were talking about the Apple fork of the Clang compiler and how to tell which version of the mainstream Clang it was based on. We said we weren't sure if there was any relationship. So Alex Mason emailed us and said, you recently asked about how the versions of Apple Clang correspond with the mainline LLVM project versions. The fork is essentially on its own path, so there's no version of Clang
Starting point is 00:02:18 that can be considered a base version for it. I learned this the hard way while trying to find a version of Clang Tidy that could run the precompiled headers from an Apple Clang build, and none of them could. I ended up disabling PCH in the CMake config. So that's what Alex has sent to us. I think the truth is probably somewhere in between. I'm sure that something from the mainline Clang is merging on some regular basis.
Starting point is 00:02:45 If only we had someone on the show that can actually tell us for sure. In fact, our guest today, Doug, was at one point at least the code owner of Clang. So do you have an opinion on this, Doug? I was. I do have an opinion. So it's not that it's a divergent fork. So the way we treat releases
Starting point is 00:03:04 is we will take from the upstream LLVM and we merge down into a fork of LLVM that's actually part of the SwiftLang project on GitHub and stabilize it and then use that as the basis for the Apple Clang that we release. So we do upstream features regularly uh to to llvm uh but there's not an exact version correspondence because apple releases compilers on different cadences from from the lvm project and um so something like a pre-compiled header for example, requires the exact same version down to the bits.
Starting point is 00:03:46 And so that is not going to translate. Yeah. So I'm going to claim that we were both right. And we'll move on from there. So thanks for that. Thanks for setting the record straight. So we would like to hear your thoughts about the show. You can always reach out to us on xMastodon LinkedIn or email us at feedback at cppcast.com. So joining
Starting point is 00:04:07 us today is Doug Greger, who you just heard from. Doug is a distinguished engineer at Apple, working on the Swift programming language and compiler. Prior to Swift, he was a code owner for the Clang compiler, an active member of the ISO C++ committee, having designed and implemented various features, including variadic templates and std function. He is a current member of the Swift's language steering group and involved in many of Swift's major features, including the generic system, concurrency model, and macro system. Doug, welcome to the show.
Starting point is 00:04:39 Thank you. It's wonderful to be here. So, Doug, I have to ask, especially since you just mentioned it, code owner for Clang Compiler, what is a code owner in that case? Yeah, so in the LLVM and Clang community, code owners are ultimately responsible for making sure that patches that come in get reviewed and for helping resolve disputes if there's technical discussions going on. So I was code owner of Clang in its very early days when it was going from barely a C parser to being a full C++11 compiler. Well, that's great. We're going to hear more about what you've been up to both in the c++ world and the swift world in just a moment but first we do have a couple of news articles to talk about so both of you feel free to comment on any of these as we go through so the first one is memory safety
Starting point is 00:05:37 in c++ versus rust versus zig which is a blog post by b shyam sundar and um although he only mentions three languages in the title you can maybe squint a bit and add a fourth one in because in the c++ sections he does also talk about circle which given we're talking about memory safety uh makes a lot of sense uh so we had the episode with sean baxter a few episodes ago where he talks about adding a borrow checker to C++, which he's done in circles. So this really shows how that actually plays out in the flesh. So the blog goes through a number of different memory safety issues and shows code side by side
Starting point is 00:06:17 and how you would actually tackle that in each of the languages. So, of course, most of the Rust code doesn't compile, and that's a good thing. And the same, of course, most of the Rust code doesn't compile, and that's a good thing. And the same, of course, for the Circle code. I thought it was great that the author included Circle there and really tried it out. It's something where it does change the programming model for C++, this safe variant,
Starting point is 00:06:41 and you really have to just try it to get a feel for the language and how it works yeah yeah absolutely my other comment was probably should have involved swift in there i don't know if you might pick up on that and well maybe we'll touch on what swift brings to the table in the same subject as we uh as we go to the interview so uh hold that thought for the moment moving on though uh we have a new conference coming up so we did mention this um maybe a month or so ago c++ under the sea so we'll see the the name play on my conference c++ on c uh this one's in the netherlands um and makes reference to the
Starting point is 00:07:20 fact that the the airport at least is below sea. We did tease at the time that there were going to be pre-conference workshops, although they hadn't been officially announced at the time. They have now been announced. All of the workshops have been detailed on the website. Three of them, all from instructors that will be known to regular listeners of this show. So we have Jason Turner going to talk about Applied Concepts.
Starting point is 00:07:43 Mateusz Puszcz going to talk about C++ concepts and also the sort of free C++20 workarounds with Sfinney and that sort of thing. And I am doing a workshop on coroutines as well. So if any of those things interest you, this was a brand new conference. It might be exciting to try it out if you are accessible to the Netherlands talking of Matush he has released the latest version of his library mp units so 2.2.0 has just been released and really I think this might have been better called 3.0 seems like quite a big release particularly particularly on the build system side. Done a huge amount of work there.
Starting point is 00:08:26 Some of it resulting in breaking changes. So do watch that if you're an existing user. But a lot of it has been in support of C++20 modules support. So finally support C++20 modules. Currently only with CMake 3.29.3, Ninja 1.11 and Clang 17. So that specific tool chain,
Starting point is 00:08:47 but there are other supported tool chains on the way. It's good to see support for modules arriving at some popular open source libraries. That is because when I think of all the talks that we have in modules, it's, you know, I've been hearing it for a couple of years, but actually seeing something pulled together on a project, especially his, you know, I've been hearing it for a couple of years, but actually seeing something pulled together on a project, especially his units library, that's excellent to see he's got
Starting point is 00:09:10 that move forward. So that is our set of news items for today. So over to Doug. So we mentioned that already that you were previously in the C++ community before you moved across to working on Swift. So maybe you want to talk a little bit about that first. So what is it you were doing, particularly on the C++ attendance committee? Sure. So I got really interested in C++ and especially using C++ for generic programming
Starting point is 00:09:40 and got involved in the committee around the time that the first C++ technical report for library extensions was starting. So this is right after C++ 98. And I came to the committee to bring in this little library I worked on called boost function that became CR one function and got interested in language design and the workings of the committee. And so I spent 10, 15 years or so working on a number of features leading up to C++11. Nice. Yeah, I remember Boost Function back when that first came out,
Starting point is 00:10:21 and that inspired me to write my own version of basically the same library. So it was quite a design playground at the time, I think. It really was. And it was somewhat of a new technique of using type erasure and finding the best ways to implement that in C++. So I enjoyed library design. And then I started liking language
Starting point is 00:10:44 and compiler implementation quite a bit more. And so I had been prototyping features in GCC. So, for example, the variadic templates implementation. And then when the opportunity came up to work on Clang, I thought, it's a good time to go and become a professional C++ compiler writer instead to work on this descent compiler. So is that when you moved to Apple to work on Clang? Yes. Yeah, Apple started the Clang project,
Starting point is 00:11:14 and it's where the center of gravity was for Clang for especially the early years where it was coming up from nothing to become a new C++ front end. We talked about modules back in the news section, but I seem to remember that you implemented a version of modules for, I think it was for Objective-C, but it basically worked for C++ as well. Am I remembering that right?
Starting point is 00:11:36 Yes. So we implemented a version of modules in Clang in 2012 or 13, and it was for C and Objective-C at the time. Actually, folks at Google, like Richard Smith, did a lot of the work to bring C++ support to the Clang modules implementation. Part of our motivation was compile time aspects. The other part of the motivation was we had a new programming language coming, and we needed to do interoperability
Starting point is 00:12:06 between C Objective-C libraries and this language, Swift. And the way we wanted to do that is through a module system. So it's possible to express C and Objective-C code as a module that could be imported into Swift and treated just like it were a Swift module. Right, right. Okay, interesting. But was that particular implementation ever proposed for standardization or was it completely independent of what we actually got? It's very different from what ended up getting standardized. So I did bring it to the committee at some point to talk through how we handled the mapping. So if you think of the C++20 module specification and the header units notion, where you can have headers together, form a module. Essentially, the Clang module system is entirely built around that. So you write this little file called a module map that describes
Starting point is 00:13:07 what headers are part of a module and what the structure of that module is. And then you can treat it as a module in Clang or in our case in Swift. Nice. So Doug, when it comes to Swift and C++, what do you think C++ programmers will recognize in Swift and what are the parts that you think they'll find are different? Yeah, so the Swift design is informed by a lot of programming languages. So if you're kind of coming from any language, you'll see something that you recognize and say, oh, yeah, I really like that feature from wherever I came from. For C++ programmers, there are a lot of common roots. So I would say in C++, we deal a lot with value types. So value types are types where when you make a copy,
Starting point is 00:13:57 you get a distinct copy such that modifications to the original don't affect the copy and vice versa. And it's a very nice system to work in because you can reason locally about what's going on, that your copies are independent. And so there are many value types in C++. You know, strings, vectors, vectors of strings, whatever, are all value types. Swift takes that same notion and brings it more to the forefront in the language itself and so a struct in the swift world is a value type so it has fields it's always copied by value you can't override the behavior by introducing like a special copy constructor it's just copied and the c++ world
Starting point is 00:14:40 i've seen this referred to as a rule of zero type, which I thought was an interesting way to talk about things. But that's exactly what Swift structs are. On the other hand, Swift also has classes. And if you're coming from C++, you look at a struct, you look at a class, you say, oh yeah, there's almost no difference here, right? Whether it's private or public by default. If you take that view into Swift, you're going to find yourself very confused because a class in Swift is a reference type. It's allocated on the heap. It can have inheritance with overriding, use the reference counting scheme under the hood to manage memory. And so it's a completely different world of reasoning than the struct
Starting point is 00:15:25 world that we generally push people toward. That's interesting. Is there anything that you think, what could C++ do you think learn from Swift? So I can tell you what features I desperately miss when I'm writing C++, knowing that they're possible. So I work in both languages. So I work in Swift. I work in C++. Much of the Swift compilers is still written in C++, although we're steadily migrating it towards Swift. And so there's two big ones.
Starting point is 00:15:58 The first is enums. So an enum in Swift is both an enum and a union from C++. So it is a type that is one of a set of different choices, and each choice can optionally have data associated with it. And then there's really nice pattern matching facilities on switch to go and check all of the cases and pull out the data from each of the choices in a very elegant way. I try to write this code in C++ and it's just,
Starting point is 00:16:34 it's a lot of boilerplate and there's a lot of extra asserts in there that, you know, you have to be really, really careful to get it right. The language just really isn't supporting you. Whereas in Swift, I can just write down the options, write down the data that goes with them, and everything is super easy and safe from there on. And I think there is nothing stopping C++ from adding a really nice discriminated union type
Starting point is 00:16:56 with pattern matching, other than it's a bunch of work and the language will get bigger. Yeah. Yeah, I think pattern matching remains a proposal in the lineup on the on the standards committee i keep hearing that it's it might be might be coming in c++ 26 but that might be optimistic at this point but it's still being worked on and i think the idea was that they wanted to get pattern matching in before they actually consider a language level variant, which is what the discriminated union would be. But that's also on the horizon, potentially.
Starting point is 00:17:31 It's a huge productivity driver for me. It's so much easier to model things. It's just structs and enums. You can model kind of your entire world that way. And it's all nice value types all the way down. I think one of the other things I miss is the error handling model from Swift. So on the surface, again, it looks similar.
Starting point is 00:17:58 A Swift function can be annotated as throws. It's going to throw something. And then you can throw an error with the throw statement, very similar to what you see from C++, but all the defaults are different. So if you don't write throw in Swift, then the function doesn't throw. There's no way around it. You have to opt into throwing. And when you do, you can throw any error. And so error is a protocol. Any type can go and conform to the error protocol,
Starting point is 00:18:30 which means it can be thrown as an error and propagate through the system with do catch blocks and so on that you used to from C++. Errors in Swift are also checked. So if you try to throw out of a function that says it doesn't throw, you won't be allowed to throw. And so it makes it a very consistent model that's easy to reason about. The last part of the reasoning is that when you call a function that can throw, you have to place the try keyword on the call. The reason for that is you're informing the reader, you know,
Starting point is 00:19:08 you and two months down the road or someone else that here an error could be thrown. And so you have control flow from that call statement. That's not to the next statement in the program. It's out of the function or it's out to the enclosing do catch block. And so this is a philosophical decision on Swift's part to try to make all of the control flow and what's going on with error handling with any control flow going on in the program, very clear when you're reading the code. Yeah. This is something that I've looked at quite a lot because I did a couple of talks on it a few years ago around the time that Herb Sutter was proposing P0709, the so-called static exceptions paper, which is very close to the SWIFT error handling model. I remember reading it and thinking, oh yeah, these all look like the same, roughly the same decisions that we made and for basically the same reasons yeah which is why i was really hopeful that it was gonna gain traction and go somewhere but it
Starting point is 00:20:10 seems to have um hit too many roadblocks and as far as i know is is not currently being pursued but um it comes comes back up in discussion from time to time so never say never but that's a shame because i've also said when i was giving these talks that actually in my view at least the swift error handling model is possibly the best error handling model of any programming language and i've not found anyone been able to review that yet because as well as all the points you've just made, there's also all the points of interoperability between different ways that you handle errors. So the Swift error handling is what looks like exceptions
Starting point is 00:20:52 to us coming from a C++ world. As you say, it's not quite, it's closer to control flow, but with similar keywords. But then you've also got the ability to deal with optionals and the result type now and and also the abort if there's a logic error but there's all these different keywords and shortcuts for getting between each of those so if you've got a function which throws an error you can treat it as if it actually returned an optional or gave you a result type and vice versa. It's almost like a freeway ability to map them all.
Starting point is 00:21:27 I've not actually found any other language that gives you that degree of control. Yeah, we found it very useful because often you're moving around. I think one of the fundamental aspects of Swift's design that would need to be introduced, I think, into C++ to get a similar model is that errors are just normal values. would need to be introduced, I think, into C++ for it to get a similar model,
Starting point is 00:21:52 is that errors are just normal values. They're a value that conforms to this error protocol, but there's nothing special about them. They're not in a special heap. You don't need something like exception pointer to try to move exceptions between threads in C++. That is really messy. It's just a value. And so if you want to rethrow that value, fine, you just throw the value. But then it also means you can package it up in this result type, which is just an enum that has either a success case with the value that's returned on success or an error case with the value that is stored on error. And you can go and call an accessor on that value of the result type to say, well, give me the value or throw the error that you already have. And that's just a try result dot value.
Starting point is 00:22:34 So it does make it very easy to move back and forth between treating the errors just as part of the value, like you might do if you're serializing the results and across the wire, or treating it as, no, this this is a control flow now and i'm going to handle errors in a separate path yeah because one observation i made is that when it comes to error handling we generally bake in the way errors are going to be handled at the uh the implementation site if you write a function then you dictate how the caller is going to handle your your error but actually very often you don't know how the error should be handled until you get to the call site and if that decision has already been made it can really limit your choices but with with swift error handling because you get that ability to move between those different modes you really get to make that choice at the call site, which I think is where most of the power comes from.
Starting point is 00:23:29 Right. Yes, it is very flexible on the caller side as to how and when you want to deal with that error. That's interesting to me because I've spent more time in Go recently than Swift, but the thing that's similar more time in Go recently than Swift. But the thing that's similar to me in Go, you've got to love tuples if you're using Go. Because everything is... But at that same point, the handling of errors, I think, kind of reaches that middle ground. Because in Go, you're going to return a value and you're going to return the error. And so in a way, what I'm hearing,
Starting point is 00:24:05 and I haven't experienced yet, but with Swift, it's kind of all as one where Go, I did learn it. It's a little nicer because errors are packaged up as an error and you have your value and you can return both of them at the same time and you're checking both. But to get them as one unit and make that decision-making, yeah, I don't like all the tuples at times, but I do like handling errors a little more when I'm using Go than I do in C++. Yeah, I think bringing C++ in there is a low bar. Yeah, I think I prefer the – generally speaking, I don't package errors and result unless I'm going to move them across some serialization interface. Because most of the time, the code path that needs to handle an error is different from the straight line code path when everything is going well. And so the tuple return for me is tougher because I always have to remember to do these two things.
Starting point is 00:25:04 And I keep having to split my control flow of handle the error now. Okay, next thing, handle the error now. Okay, next thing, handle the error now. Rather than being able to just put the error handling code off to the side and treat it as one block. Yes, there is a lot of repetitiveness when you look at the returns from Go functions. Yes. Yeah, I think the result type was only really introduced later to cater to things like async code and serialization and that sort of thing.
Starting point is 00:25:34 Yes. So the result type wasn't initially part of Swift when the error handling model came in. It was invented by many, many, many people simultaneously. Guilty as charged. And a lot of that was for asynchronous code where you have a callback and the callback has to say whether it succeeded or failed.
Starting point is 00:25:56 And a result type is very good for that. Result came into the standard library to standardize all of these disparate implementations of results that we all have a currency type to work on. Now, interestingly, the result type is less important now that Swift does have a concurrency model that involves async await. Because now an asynchronous function can be a throwing function. And the error, if the asynchronous function call fails with an error will be propagated in exactly the same way as if you were calling a synchronous function so talking of
Starting point is 00:26:31 asynchrony um there's a sort of a logical progression onto concurrency and we're going to come to that in a moment because i know there's been some big developments on on that side before we get to that because this is a c podcast, just to bring it back around to C++ again, what's the interoperability with Objective-C in both directions to the point where you could add one Swift file to your Objective-C project and start using Swift in that code, call all of your Objective-C, call back to the Swift, and so on. This made it really easy for people to get started
Starting point is 00:27:20 and eventually slowly migrate over instead of requiring some massive rewrite to try out the language. So we're doing the same thing with C++. And now the basic model here is the Swift compiler embeds a complete copy of the Clang compiler in it, which is how it's able to understand all of the C and Objective-C and C++ code in the world.
Starting point is 00:27:42 And then Swift will take that C++ header++ headers you can feed it a header or give it a module a clang module with c++ code in it and then swift will map the c++ interfaces over to their nearest equivalents in swift right allow you to directly call those C++ functions. So there's no extra wrapping step or wrapper generation step. It's all handled in the compiler in both directions. So when you said module back there, that's your modules? Or C++20 modules? It is currently Clang modules.
Starting point is 00:28:22 Right, right. Okay. As the C++20 modules support improves, we will support that as well. Everything's waiting on modules. It's a big change to the ecosystem. The way that we did Clang modules with the module maps as an overlay is very much a, we can't wait for the ecosystem to change. We need a way to take what exists there today and make it work. But so back to C++ interoperability.
Starting point is 00:28:53 So you can bring in C++ interfaces into Swift. We will do our best to map things. So for example, if you have a C++ class, it has, you know, copy and move constructors, destructors. We will bring that into Swift. It can be a value type. And we will appropriately call the copy constructors when needed to copy a move if we know that we don't need it at the end. You know, the destructor at the end so that you get the C++ semantics just within the Swift language for the same APIs.
Starting point is 00:29:26 And the idea here is you should be able to get started working with Swift quickly. Take your existing C++ library and just start using it. Often the first thing you do is wrap it up in nicer Swift interfaces. But at least you're able to do that without extra steps in between. Yeah, that's nice. And you started that whole section by talking about interoperability with Objective-C, which on the surface may seem irrelevant, but that was a massive success story. As you almost completely displaced the Objective-C ecosystem at this point. I mean, there's still legacy code out there,
Starting point is 00:30:06 but it's running fine just alongside new Swift code. There was nobody writing new Objective-C code today. They've just all moved over. So if you can bring that same, maybe not quite the same degree of interoperability, but something close to that for C++, that positions you as the ideal successor language, I think. That's our view. So we launched Swift as the successor to Objective-C, and really clean
Starting point is 00:30:32 interoperability is a critical part of that story. With C++, it is the same. It's a little more difficult. There's a couple of reasons for that. Certainly certainly c++ is a much bigger language than objective c so for folks that don't know objective c uh take c and put a small talk layer on top of it to add object-oriented programming it's actually a fairly small addition on top of c and so uh building interoperability layer it's it's work but it's a smallish language. C++ is not a small language. And it's not even that you're just worrying about how do you map the language constructs, because you also want to map the idioms. If you take a C++ class, some of those are value types. They should be maps as structs and use the copy constructor and make copies when needed. Some
Starting point is 00:31:26 of those classes are actually reference types. You're expected to only use them via pointer, maybe only via a unique pointer, only by a shared pointer. To build a really good interoperability experience, you need to be able to deal with those idioms and mapping idioms well.
Starting point is 00:31:42 In addition to just the underlying language constructs, like how do I call a method on C++ class? So this is going to be a very long project. I'm not sure it can ever end. Not a Swift project then. But it's not a Swift project, no. But year over year, we make make more things easier make the experience better
Starting point is 00:32:07 and then that that helps improve code bases that are in this hybrid state which could last for decades right if you're taking existing code base and migrating to another language and make that something that programmers can be productive in so i I'm curious, how does that work from a performance point of view? I mean, a lot of times we think of C++ because, you know, close to the metal in performance. So when you're pulling that in and compiling with, you know, again, I get you're pulling the whole Clang compiler in and with Swift, but how does that change performance? Or does it? It can. So when the Swift compiler pulls in C++ code and the form calls to it, it is a direct call to that.
Starting point is 00:32:53 So there's no extra marshalling in the middle. It is a direct native call, right? These are both native compiled languages. We directly tie into clang so that we match calling conventions exactly. No extra work done. However, there are places where Swift's model may require something that doesn't line up exactly with C++. For example, Swift might introduce a copy for safety reasons to make sure that if you pass something into a constant reference and something else might modify it, the thing to do for memory safety is you actually have to make a copy
Starting point is 00:33:31 to make it fully safe. However, that can have performance impact. And so this is one of the more interesting technical issues we're navigating of how do we provide the best safety for C++ APIs when used from Swift? Because you're coming from a language that has memory safety built in, and you're tying to a language that doesn't have it now. And you have to find the right balance between maintaining safety guarantees wherever you can, but not doing something that's a surprise to a user of that C++ API, such as introducing a large number of extra copies.
Starting point is 00:34:10 And so we're working on the balance there. And I think that that's going to take a little while to resolve. And it's kind of a fundamental challenge with two languages that have many similarities, but have different underlying mental models. So if you're coming from a background where you're, say, you've got some high-performance C++ libraries, and then you wrap that in, say, Python to get some productivity from the developers,
Starting point is 00:34:38 if you wanted to do the same thing but with Swift at the upper layer, that should be much less of an overhead than the Python root because you're calling directly and you're already native. You're calling directly, you're in native code already. The optimizers can help you. So from that perspective, the overhead is very low, but compared to, I don't know, say Circle or possibly Carbon,
Starting point is 00:35:03 I don't know, some of the other successor languages, there may be a little bit more to it. Generally speaking, the overhead tends to be low, but we like to look in case there are extra copies being introduced to satisfy the memory model. We're also investigating improvements to the ownership system that would allow us to eliminate many more of those
Starting point is 00:35:23 without compromising on the safety guarantees or surprising a C++ programmer using that API. What about the generic system? Because that works very differently to C++ templates. Do you map those at all or is that just something you stay away from? The generic system is tricky. So they are very different. So Swift has a generic system. It has generics that are similar to templates, but the compilation and type checking model is 100% different.
Starting point is 00:35:57 So Swift generics are separately type checked and are separately compiled, although that can be optimized away. And so C++ has an instantiation model where every instantiation of a type could be completely different internally. A vector of integers and a vector of strings in Swift, those are different types,
Starting point is 00:36:19 but the bodies are fundamentally the same. Whereas on the C++ side, those are two different instantiations. And if you want to make C++ programmer cry, you say vector bool, which is a completely different animal, right? That kind of change isn't possible in Swift, where you have just completely different representations.
Starting point is 00:36:38 So for mapping templates, it's a little bit tricky. Right now, we ask that you build a typedef or an alias declaration to give a name for a template you want to instantiate in C++, in Swift, and that treats these as distinct types. And so we've been looking toward more of a unified model to allow things like instantiating C++ templates on Swift types. That is absolutely possible. It's just technically interesting to do because at that point, you're essentially doing both directions of interoperability at the same time. You're exposing your Swift type into a C++ type that has some expected interface such that
Starting point is 00:37:26 a C++ template won't stand you on it. Yeah, that brings to mind when we were discussing the Epochs proposal for C++ back in 2020. I don't know if you followed that one, but... I did not. I can take a guess, but... Yeah, it was meant to model or be similar to uh rust editions and allow you to make changes to the language between versions that were breaking but work with previous versions as well so great idea everyone's on board with it until we actually dug into how this would work and of course it was templates that support the party and uh if c++ can't be compatible with c++ that way i'm surprised that you're able to to pull that off with with swift as well yeah c++ templates and in general inline code in headers could make that very difficult so swift does have have this same notion. We just call them language modes, where the Swift 5 language mode describes a certain set of semantics and syntax and so on.
Starting point is 00:38:34 And we can introduce a new language mode whenever we want, although we'll only do it for big reasons. And a new language mode, like language mode 6, can make breaking changes to the source code. So it can reinterpret some old source in a new way or reject some old source. And so you have to write it differently. Now, the critical piece to actually make this work is you can't require everyone to move forward at the same time, because that will never happen in any sizable ecosystem. And so we allow you to mix and match language modes on a module per module basis. So if you want to adopt the new Swift 6, for example, you can do so in one module. And the code in that module is subject to Swift 6's rules,
Starting point is 00:39:25 but it can be used from a Swift 5 module and it can use any number of Swift 5 modules. And so you can introduce this new language mode and let it sort of slowly ripple through the ecosystem by the most motivated people first, right? They'll move quickly. And eventually you hope to converge on the world has moved. But it lets you correct mistakes. It lets you expand on the safety invariance that you have
Starting point is 00:39:48 in a way that is very difficult to do in something like C++ where all of your code is in headers that need to compile in every mode. Yeah, yeah. And that is an almost perfect segue into our next topic because you mentioned Swift 6 and Swift 6 mode and breaking changes that come along with that. And there's a big feature that's really taken advantage of that in Swift 6, which is strict concurrency.
Starting point is 00:40:15 Do you want to explain a little bit about that? For sure. Right. So Swift Language Mode 6 is coming now with the upcoming Swift 6 release. And it is the first language mode we've introduced since 2019. So we've been on Swift 5 for a very long time. And the goal behind Swift 6 is to expand on the memory safety guarantees that Swift had and into data race safety guarantees. So strict concurrency is about statically detecting where you have data races in your
Starting point is 00:40:52 program, such that if you have a Swift 6 program that compiles the entire thing as Swift 6, you will be free from data races unless you've opted out of the model somewhere, right? In C code or in some Swift 5 code, for example. So how is that achieved then? What do you have to do in your code to take advantage of that? Yeah, so the Swift concurrency model, the basic idea here for detecting sort of problems with data races is you need to know where concurrency is introduced and what is shared between different concurrently running tasks. Right.
Starting point is 00:41:38 The textbook definition of a data race, of course, is you have two accesses to the same shared mutable state, and at least one of them is a right. And so in the Swift model, we know at what point concurrency is introduced. So there are primitives for create a new concurrent task. And there are primitives for, you know, I'm packaging up this function and it's going to run somewhere else in some other thread. And so we know concurrency is introduced. And there's also a set of constructs to understand what kinds of things are safe to share when you do that. And so, for example, if you have an integer, you can copy the integer into this concurrent task. And it's fine, because you have the copy.
Starting point is 00:42:34 This is where value semantics is very useful. You can copy a string. It's fine. You can copy a dictionary, a hash map of all value types from one thread to another. and the two copies can be accessed independently. That's fine because there's no sharing. Now we have to describe that somehow in the language. And so we have this notion of sendable types. So a type is sendable if essentially it can be sent. So a value of
Starting point is 00:43:07 that type can be passed from one concurrency domain to the other, and it is safe to use both the original and the copy concurrently. Value types are the easy case. There are other language primitives that also can provide this guarantee. For example, you can have a reference type that knows how to synchronize its own state. The one that's built into the language are actors. Actors come from this language called Erlang. Actually, they're used in many systems, but they're popularized by Erlang. The idea is an actor is this entity that can be
Starting point is 00:43:46 referenced, though it's a reference type, and it protects its own state. So no one can touch its state directly, and it ensures that only one thread or one concurrent task is running on the actor at any given point in time. And so this is actually a new type that came into the Swift language when we introduced the concurrency model. You can declare an actor, and the language guarantees that you cannot read or write the instance data from that actor from anywhere else without going through the synchronization mechanism for that actor, which in the language itself just means you have to await when calling functions on the actor that touch that state. So the last episode, but one, we talked about the senders-receivers proposal
Starting point is 00:44:41 for C++, which I believe is a very similar model. Is that right? Is it the same sort of model? Are there differences? It's a similar model. I feel like these models may have similar inspirations, but went a different way. So I know in that episode,
Starting point is 00:45:03 you talked a bit about structured concurrency as you know, the, the used a lot within the sender receiver paradigm to sort of control computation. You describe the computation, any concurrent tasks that are going on in the computation are sure to end by the time that the computation ends. Uh, with Swift, we also support structured concurrency. It's actually a language facility, and it's language in library facility. So within an asynchronous function, you can define a bit of computation to run concurrently. There's two mechanisms. One is an async let.
Starting point is 00:45:39 Let is essentially the way we introduce a new value that is immutable so it can't change once it's assigned and then you always assign some initial value you can make a let async and what that means is go compute the initial value of that thing in a separate concurrent task and then later when you want to read that you have to await it which means your task suspends until that other task completes and gives you the value. And so what this does is it makes concurrency very similar to normal code in Swift. And so, you know, async await is about making asynchronous computation into just normal control flow. All the callbacks and completion handlers go away, and you just await the result and suspend the task when needed.
Starting point is 00:46:28 Structured concurrency, at least in the Swift form, is about taking bits within an async task and creating concurrent tasks to run those while still ensuring that everything finishes up in that same function to provide control flow. So I would say that Swift concurrency model, unlike senders receivers, which is its own model of structured concurrency
Starting point is 00:46:54 that then can tie back into the coroutines in C++, Swift is completely tied to our async 08 model and integrated with that model. I remember in that episode, we mentioned coroutines and how a coroutine is a type of sender and so can be awaited on. And I was wondering if that same model applied with sendables. So sendables are the values that are passed into a task. Right. They are not the task itself.
Starting point is 00:47:36 Okay. Yeah, that makes sense. And so in Swift, you can create a task and imagine this creates a completely new execution context that runs a function and returns a result. And it's going to go do that concurrently with everything else you do. To start a task, you usually have to send in some data. To do so, you need to pass in data that is sendable. Otherwise, you would be introducing a race condition between the task that you just started and the code that may still be using that value. Now, we do have some low analysis in the compiler such that if you just construct values and send it off to a new task and never touch those values again, we won't complain about that, even if it's not sendable.
Starting point is 00:48:19 Because we realize that you've just built something up, some data structure, and sent it off on its way, and you're not touching it. So that makes it a little less strict than what it sounds like from the Sendable discussion. But the core model is Sendable things are those that are safe to share across different concurrent tasks. So if you have your own data structure, how can you communicate to the language, I presume, that it is sendable?
Starting point is 00:48:46 So sendable is a protocol. And so in Swift, to conform to a protocol, to say, you know, I satisfy the constraints of this protocol, you have to explicitly state either on the type itself or on an extension of that type that, you know, my data structure conforms to that protocol. In this case, if you state my data structure conforms to sendable, then now it can be used as sendable. However, if we just allowed you to do that willy-nilly, you'd be making a promise that
Starting point is 00:49:21 we're not checking, right? That would undermine the model. And so when you declare that a type is send we're not checking. That would undermine the model. And so when you declare that a type is sendable, the compiler is actually going to check that. So what that means differs depending on what kind of type you're talking about. So if you're talking about an actor, it's already sendable because it is guaranteed to manage its own state. That is what the compiler does for you.
Starting point is 00:49:43 If it's a structure and enum something that's a value type compiler is going to recursively check are is all the data that goes into your structure your enum also sendable because if so it's safe to send that whole bundle right if not if you have you know some reference to a non-sendable type or class somewhere buried in that in that structure, then it's not going to be safe to mark it sendable and the compiler will reject it. When we talk about generic types, you can propagate this information through also by stating conformance. You can say something like, if I'm defining the result type or the optional type. I can say, well, my result type is sendable
Starting point is 00:50:25 when both the success type and the error type are sendable. Right. Makes sense. So would it be correct to say that potentially it could have been achieved without having to use this protocol, but then the compiler would have to check everything and it'll be a lot more work for the compiler. Just to narrow the focus on the things that you claim to be sendable.
Starting point is 00:50:51 So we could infer sendable, and we have some inference for, even if you didn't write sendable on, say, a struct of sendable things, the compiler can infer that it's sendable and allow it. However, we did not want to do this across module boundaries. This is us stepping back and looking at the ecosystem, because if we infer it everywhere, we have problems over module boundaries, because maybe you rely on some library,
Starting point is 00:51:24 and today it happens to be sendable, because things are encoded in integers. And you depend on it being sendable. Later on, the author of that library says, oh, no. These integers, these are references into a global structure. You can't share this. This will break.
Starting point is 00:51:44 And would have to turn off sendability at that point, thereby breaking all of their clients. So stepping back, we could do inference, but it's better that we don't do inference across module boundaries. Within a module, it's perfectly fine. Under the assumption that within a module, you can fix things yourself, right? If you go ahead and say, oh, wait, now this is non-sendable, you broke it, but you have all the tools to fix it. Not so when it's across module boundaries and might not be code that you own or conversion independently. Makes sense. Yeah, a bit like constexpr in C++. We could infer constexpr, but then you might change the code and it's no longer context pro and it breaks code that it's a hiring flaw at the end of the day yes right any any promise you implicitly make yeah on the library that library author is probably going to be stuck with forever if they
Starting point is 00:52:40 have a significant number of users and so a lot of Swift is designed with that in mind in the sense that, like the Swift access control model, we just write code within one module. You'd never have to think about access control. You will never write public. There's no point in it. It's only at the point where
Starting point is 00:53:00 now you have two modules and they're independent that you have to think, well, wait a minute, what is it that I actually want to provide to clients of my module? And those become, you mark those as public to make them available, but it's always opt-in so that you're never locked into an implementation detail and unable to refactor or change your code under the hood.
Starting point is 00:53:23 Yeah, that makes a lot of sense. Right. And since you mentioned protocols just a little while back there, I think that's another thing that's just worth digging into a little bit because on the face of it, protocols you might think of as just being like an interface. But I know there are a lot more than that, maybe more akin to rough traits or, since I know there are a lot more than that, maybe more akin to rough traits,
Starting point is 00:53:46 or since I know you worked on it, C++ OX concept maps. Is that something you want to talk about at all? Protocols are indeed very similar to the C++ OX concepts. So a protocol describes an abstract interface that can be implemented by any number of types. So this can be instance data, it can be methods on there, you can have operators, whatever you want. And the all generics in the Swift language
Starting point is 00:54:22 are expressed in terms of protocols. So if I make something generic over type T, I can't do anything with T because it's separately type checked. There's nothing I can do on a T other than copy it around. To actually do something with T, I say, well, okay, T, the generic type T must conform to some protocol P. And then I can use any of the operations specified in P on the values of that generic type.
Starting point is 00:54:49 I can pass it to other generic functions that are described in terms of P. And it makes for a nice separately type-checked model. This is where the C++ OX concepts were going. C++ OX concept maps were a way of adapting syntax. So if the type that you had didn't exactly match the concept, you could write in your concept maps definitions that bridge the gap. In Swift, we have the notion of extensions that can do the same thing.
Starting point is 00:55:22 So you can extend a type, give me a new interface, which includes adding whatever new members to it you want. This is really common in the Swift world to go extend a type with your own convenience functions. Get that nice, you know, X dot Y is in tax. We have,
Starting point is 00:55:36 we like, but an extension can also declare conformance to a protocol, which includes putting in, adding any members that are needed to make it satisfy the requirements of that protocol. So protocols are deeply ingrained in the way generic programming is done in Swift. In fact, you can also use those two features together. You can extend a protocol, which means you're implementing a generic algorithm on all types
Starting point is 00:56:04 that conform to that protocol. Yep. So if you think of, well, if you think of like the C++ standard template library, you have a bunch of containers and other range types, and then you have a bunch of free functions that operate on them and are generic, right? They're templated over the container type, I guess we should say the range type. In the Swift world, you have similar to the container notion,
Starting point is 00:56:31 you have a collection protocol. But instead of having, you know, free functions that work on collections, you just extend a collection and add whatever functions you want there. And so all the algorithms are actually like for things like arrays and dictionaries, like all the collections are actually right there in the collection protocol as generic algorithms. It's quite a nice system to work in. It is similar in spirit to what Rust traits are doing,
Starting point is 00:56:59 although there are lots of little differences in how the languages work. Yeah, I'm not sure if the average Swift developer truly appreciates the power of protocols because they've just always had them, I suppose. But I do remember when protocol-oriented programming was introduced to WWDC by none other than Dave Abrahams, who's now back with us over on the
Starting point is 00:57:25 semi-C++ side of the fence, at least. Or at least outside Apple. Yeah, everyone got too sidetracked by his Crusty the Clown persona, I think. For those who haven't seen it, maybe I'll put a link in the show notes so you know what I'm talking about.
Starting point is 00:57:44 It was a very profound moment for those that really got it anyway. Yeah, I think it's true that Swift programmers are just internalized how protocols work. And it's a very interesting and different situation from C++. C++ templates are still scary to a lot of C++ developers because of the error messages, because you have to do everything right or it falls apart on you in ways that are hard to debug.
Starting point is 00:58:17 Swift's generic system is not like that. A couple of days into programming Swift, you're probably writing protocols and extending them and you're doing generic programming. And because it is separately type checked, it's not actually much different or more difficult than working with non-generic code. It's interesting that you say that because, again, coming to Swift, when we were at Swift craft, you know, a few of the people that I even interviewed with it's, you know, there's a lot of people that Swift was and is their first language. And so it's the only thing they know, but as we're talking about protocols and you're talking about the generics and you're talking about template
Starting point is 00:58:59 errors as I'm just cringing, cause I agree even, you know, for the years I've been in, I, I don't like writing template code. I'm, I'm just that much more excited to get more into my Swift apps because even colleagues on our team, they're like, you know, we write a lot of C++ code and I have some that do a lot of work in Swift on their own projects and they're the same way. And listening to you talk about it, I can understand why. It it's like there's just so many things that make swift easier and more enjoyable to write it's certainly what we try for maybe we tried to build a language that can be your first language but can grow with you to you know build interesting libraries and and sort of tackle any domain that you're interested in building code for so i was going to get into existential types and witness tables but oh look at the time tackle any domain that you're interested in building code for.
Starting point is 00:59:49 So I was going to get into existential types and witness tables, but oh, look at the time. We should probably start wrapping up. There were actually a few other things that I had wanted to talk about, but maybe we'll have to do a part two sometime. Our traditional final question is to ask if there's anything else in the world of C++, but we could also make it Swift, that you're particularly interested or excited in. Maybe I'll answer for both. Okay. So on the Swift side, I'm most excited about our work in embedded Swift, which is where we're taking Swift. We have a subset of the language
Starting point is 01:00:25 that is most of the language and feels just like regular Swift, but it compiles down to teeny tiny, yet still safe code that you can run in an OS kernel or in firmware. And I'm excited about it because this is taking the language down to places that we always thought it could go but
Starting point is 01:00:46 now we have the existence proof that it actually works and has been really fun to work on does that like what we call on the c++ side freestanding it is similar to freestanding right yes so you you need to supply your own runtime entry points but only for the features you care about nice okay yeah well we definitely have to expand on that in part two whenever we do that and you mentioned you do both languages so i did so for c++ i am very interested in what comes of the metaprogramming uh proposals and the reflection proposals specifically because I felt like I always wanted reflection capabilities in C++ and the dynamic facilities with type ID were never useful
Starting point is 01:01:33 for doing anything serious. And it seems like we do a lot of pre-processor metaprogramming because we don't have reflection. And I feel like this could be a huge improvement for building much better libraries in C++ if we get some great reflection facilities in the language. Yeah, no, I think you're in good company there. Okay, well, thanks, Doug, for taking your time to come and talk to us today about Swift and its evolution and relationship with C++ all very insightful we'll see whether it takes over the world
Starting point is 01:02:11 but at least for today thank you for coming on the show thank you it's great to be here and thanks Kevin for the co-hosting thanks so much for listening in as we chat about C++ we'd love to hear what you think of the podcast. Please let us know if we're discussing the stuff you're interested in. Or if you have a suggestion for a guest or topic, we'd love to hear about that too. You can email all your thoughts to feedback at cppcast.com. We'd also appreciate it if you can follow CppCast on Twitter or Mastodon. You can also follow me and Phil individually on on twitter or master all those links as well

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.