Streaming is available in most browsers,
and in the Developer app.
-
What's new in Swift
Join us for an update on Swift. We'll take you through performance improvements, explore more secure and extensible Swift packages, and share advancements in Swift concurrency. We'll also introduce you to Swift Regex, better generics, and other tools built into the language to help you write more flexible & expressive code.
Resources
- Celebrating learning experiences from the 2021 Swift Mentorship Program
- Contribute to Swift
- Diversity in Swift
- Swift Mentorship Program
Related Videos
WWDC22
- Create Swift Package plugins
- Demystify parallelization in Xcode builds
- Design protocol interfaces in Swift
- Eliminate data races using Swift Concurrency
- Embrace Swift generics
- Explore more content with MusicKit
- Improve app size and runtime performance
- Meet distributed actors in Swift
- Meet Swift Async Algorithms
- Meet Swift Package plugins
- Meet Swift Regex
- Swift Regex: Beyond the basics
- Visualize and optimize Swift concurrency
-
Download
♪ Mellow instrumental hip-hop music ♪ ♪ Hi, I'm Angela. And I'm Becca. Welcome to what's new in Swift! We're really excited to talk to you today about all of the great new features in Swift 5.7. Many of the things we'll talk about today demonstrate Swift's goal to make your life as a developer easier. We'll look at new tooling to help you customize your workflow and some amazing under-the-hood improvements. Then we'll talk about the latest in Swift's concurrency model and the road to Swift 6, including full-thread safety. Then I'll finish up by taking you through some language improvements that make Swift easier to read and write, including cleaner, simpler generics, and powerful new string processing facilities. But first, let's start by talking about one of the things that makes Swift so special -- all of you. Your input and contributions are what have enabled Swift to expand so rapidly. Community involvement is at Swift's core. This year, more of the Swift project became available to the community when docC -- the documentation generation tool announced last year -- and the Swift.org website were open sourced. Open source works best when you have an active community shepherding it. We've been using the workgroup model for Swift on Server and Diversity in Swift to provide stewardship and support for community members interested in specific areas. This has been working really well so we've started two new workgroups. One for iterating on the Swift website and making it more of a community resource, and another for C interoperability, to shape the design of the model between C and Swift. As we venture into new areas, we all need support from members within the community. As a part of that, the Diversity in Swift workgroup introduced the Swift Mentorship Program last year. The program provides pathways to contribute to all of the workgroup areas for folks who don't know how to start or are looking to deepen their expertise in a particular area. Last year's program was a huge success. There were a lot of interested mentees; and with that, we were able to create 41 mentorship pairs. This success is why the program is being brought back for year two. The program would love to include everyone who's interested; but to do that, we need you -- the excited and experienced developers listening now who are ready to share their breadth of knowledge and make new connections. Because the mentorship program is not just about the code but about building relationships within the community. And a little guidance can have a lasting effect. Don't just take my word for it. Last year, Amrit participated in the mentorship program and focused on compiler and language design. What started off as intrigue for Amrit transformed into tangible contributions. Diving into a new domain is not easy. Even so, she walked away finding success and feeling inspired to contribute more. Like many others, this experience opened a door for Amrit. In addition to compiler and language design, last year there were a wide range of available focus areas, from technical writing and testing to contributing to Swift packages. This year, we're adding even more and there's always opportunities for new topics. If you don't see something in this list that interests you, you can still mention it in your application. Another addition is that this year's program will offer mentorship year-round for starter bug contributions to help accommodate anyone who may have a lower capacity to participate but is still excited to get involved. If you're interested in applying, or just eager to hear more, check out the most recent Swift blog post. There, you can find links to detailed reflections from the highlighted mentees. The mentorship program is just one initiative under Diversity in Swift umbrella. To learn more about the mentorship program and other Diversity in Swift efforts, you can visit Swift.org/diversity. To open the door even further, we want to make it as easy as possible to use Swift with the resources you have! We have streamlined the Swift toolchain distribution process for the Linux platform by adding support for Linux package formats. With the new native toolchain installers, you can now download RPMs for Amazon Linux 2 and CentOS 7 directly from Swift.org. These toolchains are experimental, so be sure to share feedback on the Swift.org forums. Swift is primarily used for building apps. However, the vision has always been for Swift to be scalable -- used from everything from high-level scripts down to bare-metal environments. To encourage Swift to be used where it's never been used before, Swift underwent some major changes this year. To make the standard library smaller for standalone, statically linked binaries, we dropped the dependency on an external Unicode support library, replacing it with a faster native implementation. Smaller, faster binaries are a huge benefit when running on event-driven server solutions. You get static linking on Linux by default to better support containerized deployments for the server. This size reduction makes Swift suitable for even restricted environments, which allowed us to use it in Apple's Secure Enclave Processor. Swift is useful from apps to servers all the way down to restricted processors; tying it all together is the package ecosystem. This year's new features in Swift packages will make your life better. To start, Swift Package Manager has introduced TOFU. No, not the delicious snack. TOFU is an acronym that stands for Trust On First Use. It's a new security protocol where the fingerprint of a package is now being recorded when the package is first downloaded. Subsequent downloads will validate this fingerprint and report an error if the fingerprints are different. This is just one example of how trust and security are built into the core of the package ecosystem to help you feel confident using it. Command plug-ins are a great way to improve the workflow for Swift developers. They are the first step in providing more extensible and secure build tools. Command plug-ins can be used for documentation generation, source code reformatting and more. Instead of writing your automation in a shell script and having to maintain separate workflows, you can use Swift! Think open source formatters and linters. Now, all of those open source tools are available within Xcode and Swift Package Manager. Command plug-ins are the glue between open source tools and Swift Package Manager. The Swift project is embracing developer tools in the open source community to provide seamless integration with your automated workflows. docC is great tool to integrate documentation into your source code. This year, it got even better with Objective-C and C support. Let's take a look at what it would take to create a plug-in with docC. Plug-ins are just simple Swift code. You can define a plug-in by creating a struct that conforms to the CommandPlugin protocol. And then you just add a function that tells your plug-in which tool you'd like to invoke. Within this function is where we want to call docC. Once you've defined your plug-in, it becomes available through the Swift PM command line interface and Xcode as a menu entry. Now, we can tell Swift PM to generate documentation and it knows to pass this action to the docC executable. It doesn't stop there. There's a second plug-in known as build tool plug-ins. These plug-ins are packages that allow you to inject additional steps during the build. When you implement a build tool plug-in, that will create a command for the build system to execute in a sandbox. They differ from command plug-ins which you execute directly at any time and can be granted explicit permission to change files in your package. Build tool plug-ins can be used for source code generation or custom processing for special types of files. With build tool plug-ins, this would be the package layout. In this example, the plugin.Swift is the Swift script that implements the package plug-in target. The plug-in is treated as a Swift executable. And you write the plug-in in the same way you write any Swift executable. You can implement your plug-in by defining a set of build commands that tells the build system what executable command to run and what outputs are expected as a result. Package plug-ins are secure solutions that provide extensibility in your packages. You can learn more about how plug-ins work and how to implement your own plug-in, in two sessions, "Meet Swift Package plugins" and "Create Swift Package plugins." As you expand your use of packages, you might have encountered module collisions. That's when two separate packages define a module with the same name. To solve this situation, Swift 5.7 introduces module disambiguation. Module disambiguation is a feature that allows you to rename modules from outside the packages that define them. Here in our Stunning application, we're bringing in two packages that define a Logging module, so they clash. To fix this for our Stunning application, you'll just need to add the moduleAliases keyword to the dependencies section of your package manifest. That way you can use two different names to distinguish between modules that previously had the same name. Swift 5.7 brings some fantastic performance improvements. Let's start by looking at build times. Last year, we told you about how we had rewritten the Swift Driver -- the program that coordinates the compilation of Swift source code in Swift. Last year's rearchitecture unlocked some really important changes that speed up builds significantly. The driver can now be used as a framework directly inside the Xcode build system instead of as a separate executable. This allows it to coordinate builds more closely with the build system to allow things like parallelization. If you're someone who loves the sound of quick builds, you can get more details in the "Demystify parallelization in Xcode builds" session. To show you how much faster builds are, let's look at some examples of how long it takes to build some of the tools we use often that are written in Swift. On a 10-core iMac, the improvements have ranged from 5 percent all the way up to 25 percent. Next, there are improvements to the speed of type checking. This year, we improved the type-checker performance by reimplementing a key part of the generics system -- the part that computes a function signature from things like protocols and "where" clauses. In the old implementation, time and memory usage could scale exponentially as more protocols were involved. For example, here, we have a complicated set of protocols that define a coordinate system, with a lot of generic requirements on the many associated types. Previously, this would take 17 seconds to type-check this code. But now, in Swift 5.7, this example is able to type-check significantly quicker, in under a second. We also have some equally impressive runtime improvements. Before Swift 5.7, we've seen protocol checking on app startup take as long as four seconds on iOS. Protocols needed to be computed every time we launched apps, resulting in launch times that got longer the more protocols you added. Now, they're cached. Depending on how an app was written and how many protocols it used, this can mean launch times being cut in half in some apps when running on iOS 16. The session "Improve app size and runtime performance" will dive deeper into how you can leverage these improvements in your own application. Now, it's time for something I'm sure a lot of you have been eager to hear about. Last year, we introduced the new concurrency model, bringing together actors and async/await. This had a transformative effect on the concurrency architecture of your applications. Async/await and actors are safer and easier than callbacks and manual queue management. This year, we further fleshed out the model with data race safety at the forefront. Because concurrency was such a fundamental and important improvement to your app's codebase, we made it possible to back-deploy these changes all the way back to iOS 13 and macOS Catalina. In order to deploy to older operating systems, your app bundles a copy of the Swift 5.5 concurrency runtime for older OSes. This is similar to back-deploying Swift to operating systems before ABI stability. Next, we've taken this model in new directions. We've introduced language features and supporting packages. First, let's talk about data race avoidance. Before I jump into that, I should probably take a step back and say that one of the really important features of Swift, is memory safety by default. Swift users can't do things with unpredictable behavior, like reading a value while you're in the middle of modifying it. In this example, we're removing all of the numbers in an array that match the same array's count. Initially, the array's count is 3, so we'll remove the 3 from the array. But once we've done that, the count will be 2. Do we remove the 3 and the 2 from the array, or just the 3? The answer is neither. Swift will prevent you from doing this because it's not safe to access the array's count while you're in the middle of modifying it. Our goal is to do something similar for thread safety. We envision a language that eliminates low-level data races by default. In other words, we want to prevent concurrency bugs that can cause unpredictable behavior. Here's another example. Using the same number's array, we create a background task that appends 0 to the array, and then we remove the array's last element. But wait, does removing the last element happen before or after we append 0? The answer, again, is neither. Swift will block you from doing this because it's not safe to modify the array from a background task without synchronizing access with something like an actor. Actors were the first major step towards eliminating data races. This year we've refined the concurrency model to push us even further towards the end goal. You can think of each actor as its own island, isolated from everything else in the sea of concurrency. But what happens when different threads want to query the information stored by each of the isolated actors? This metaphor will be explored in depth in the session "Eliminate data races using Swift Concurrency." From memory safety to thread safety by default; that is the goal for Swift 6. To get us there, we first improved last year's concurrency model with the new language features I just mentioned. The second thing I haven't mentioned yet is the new opt-in safety checks that identify potential data races. You can experiment with stricter concurrency checking by enabling it in your build settings. Let's take a look at actors again. We can take this notion of actor isolation, and take it further with distributed actors. Distributed actors put those islands on different machines with a network between them. This new language feature makes developing distributed systems much simpler. Let's say you want to create a game app; you can now easily write the back end in Swift. Here, the distributed actor is like an actor but it might be on a different machine. In this example, we're looking at computer player that will maintain state during a game with a user. The distributed keyword can also be added to a function that we expect will need to be called on an actor that might be on a remote machine. Let's add another function called endOfRound. It will loop over the players and call makeMove on each one. Some of these players might be local or remote, but we have the benefit of not needing to care about which is which. The only difference from a regular actor call is that a distributed actor call can potentially fail because of network errors. In the event of a network failure, the actor method would throw an error. So, you need to add the try keyword as well as the usual await keyword that's needed when you call a function outside of the actor. Building on these core language primitives, we also built an open source Distributed Actors package that is focused on building server-side, clustered distributed systems in Swift. The package includes an integrated networking layer using SwiftNIO and implements the SWIM consensus protocol to manage state across the cluster. The "Meet distributed actors in Swift" session will go into more details on how to build distributed systems with these new features. We also launched a new set of open source algorithms to provide easy out-of-the-box solutions to common operations when dealing with AsyncSequence, which was released with Swift 5.5. Releasing these APIs as a package gives developers flexibility in deploying across platforms and operating system versions. There are several ways to combine multiple async sequences and to group values into collections. These are just some of the algorithms included in the package. Check out the "Meet Swift Async Algorithms" talk to see how you can use this new powerful API. But there's another aspect of concurrency, which is performance. This year, with actor prioritization, actors now execute the highest-priority work first. And continuing our deep integration with the operating system scheduler, the model has priority-inversion prevention built in, so less important work can't block higher-priority work. Historically, it has been really hard to visualize the performance impact of concurrency in your app. But now, we have a great new tool for doing exactly that. The new Swift Concurrency view in Instruments can help you investigate performance issues. The Swift Tasks and Swift Actors instruments provide a full suite of tools to help you visualize and optimize your concurrency code. At the top level, the Swift Tasks Instrument provides useful statistics, including the number of tasks running simultaneously and the total tasks that have been created up until that point in time. In the bottom half of this window, you can see what's referred to as a Task Forest. It provides a graphical representation of the parent-child relationships between tasks in structured concurrent code. This is just one of the detailed views for the Swift Actor Instrument. To learn how to use this exciting new tool, you'll want to hop over to the talk "Visualize and optimize Swift concurrency." And don't forget to give those new packages a try. Don't be shy to let us know how it's going on the forums. Now, I'll hand it over to Becca to talk about the many improvements to Swift language usability. Languages are tools, and there's a funny thing about tools -- they can really affect the things you build with them. When all you have is a hammer, you're going to build things with nails instead of screws. And even if you have a full set of tools, if your hammer has a big, grippy handle while your screwdriver is plasticky and hard to hold, you might still lean towards the nails. A language is the same way. If Swift has a good tool for expressing something, people will use it more often. And this year, Swift's tools for expressing what you want your code to do have improved in many ways. Some of these changes are simple conveniences for things you do often. For example, it's really common in Swift to use if let with the same name on both sides of the equal sign. After all, there probably isn't a better name for the unwrapped value than the name you gave the optional one. But when the name is really long, that repetition starts to get cumbersome. You might be tempted to abbreviate the name, but then your code becomes kind of cryptic. And if you later rename the optional variable, the abbreviation might get out of sync. Swift 5.7 introduces a new shorthand for this common pattern. If you're unwrapping an optional and want the unwrapped value to have the same name, just drop the right-hand side. Swift will assume it's the same. And of course, this also works with guard, too, and even while, for that matter. We also looked at places where a feature suddenly stops working when you make a minor change. For instance, Swift has always been able to figure out what type a call will return based on the code written inside a one-statement closure. In this compactMap call, the closure returns the value of parseLine, and the parseLine function returns a MailmapEntry, so Swift can figure out that entries should be an array of MailmapEntry. This now works for more complicated closures that have multiple statements or control flow features. So you can use do-catch, or if...else, or just add a print call, without having to manually specify the closure's result type. Another thing we looked at is danger flags that aren't really flagging any actual danger. Swift is very concerned with type and memory safety. To keep you from making mistakes, it never automatically converts between pointers with different pointer types, nor between raw pointers and typed pointers. This is very different from C, which allows certain conversions. For example, you can change the signed-ness of the pointee, or cast any pointer to char star to access it as bytes, without violating any of C's pointer rules. But sometimes these differences in pointer behavior will cause problems when a C API is imported into Swift. The original developer may have designed their APIs with slight mismatches that are handled by automatic conversions in C but are errors in Swift. In Swift, accessing a pointer of one type as though it were a different type is very dangerous, so you have to describe what you're doing very explicitly. But that's all pointless if we're passing the pointer directly to C, because in C, that pointer mismatch is perfectly legal! So in this case, we've treated something really straightforward as though it were dangerous. This matters because, as much as Swift values type safety, it also values easy access to C-family code. That's why C and Objective-C interop are so rich and seamless, and it's why the Swift project formed the C working group Angela mentioned earlier to start building equally capable C interop. We don't want using C functions like these to be unnecessarily painful. So Swift now has a separate set of rules for calls to imported functions and methods. It allows pointer conversions that would be legal in C even though they normally aren't in Swift. That way, your Swift code can use these APIs seamlessly. So far we've talked about small improvements to the tools you already had. But this year, Swift also has a brand-new tool for extracting information from strings. Here's a function that parses some information out of a string. This sort of task has always been a bit of a challenge in Swift. You end up searching, splitting, and slicing over and over until you get what you want. When people notice this, they tend to focus on the little things, like how wordy it can be to manipulate string indices, but I think that's kind of missing the bigger picture. Because even if we changed this syntax, it doesn't help you answer the basic question you're asking when you look at this code -- what does the line variable that's passed into it actually look like? What sort of string is it trying to take apart? If you stare at it long enough, you might realize that it's parsing a simplified version of a mailmap -- a file you put in a git repository to correct a developer's name in old commits. But extracting that information by searching and slicing is so involved that it's hard to figure that out. You get so lost in how to slice up the string that you kind of lose track of what that string is. The problem is not these two expressions; the problem is the whole thing. We need to rip out all of this and replace it with something better. We need a different approach; one where your code sort of draws a picture of the string you want to match, and the language figures out how to do it. A declarative approach, not an imperative one. In Swift 5.7, you can now do that by writing a regex. A regex is a way to describe a pattern in a string. For over 50 years, languages and tools have allowed developers to write regexes in a dense, information-packed syntax. Some of you already use them in the Xcode find bar, in command-line tools like grep, in Foundation's NSRegularExpression class, or in other programming languages. That syntax is now supported by Swift's regex literals, and it works just like it does in any other developer tool. But some of you haven't used regexes before and you're probably going, "Is that real code or did a cat walk across her keyboard?" And I don't blame you. Regex literals are written in symbols and mnemonics that you have to memorize in order to read them. To someone who knows the language, even the gnarliest parts of this regex, like the part that matches the developer's name are just combinations of several simple matching rules. But that's a lot of behavior to cram into 11 characters. Regex literals are so compact that even experienced developers sometimes need a minute to understand a complicated one. But what if you could write the same kind of matching rules, just with words instead of symbols? That seems like it'd be easier to understand. In fact, put it all together, and you get something that looks a lot like SwiftUI. That'd be a great alternative to a regex literal, wouldn't it? So it's a good thing Swift supports that! The RegexBuilder library provides a whole new SwiftUI-style language for regexes that's easier to use and more readable than the traditional syntax. It can do the same things a regex literal can, but it describes its behavior in words you can understand or look up, instead of symbols and abbreviations you have to memorize. Regex builders are great for beginners, but this is far from a beginner-only feature. It has powerful capabilities that go way beyond what a regex literal can do. To start with, you can turn a regex into a reusable regex component, just as you can turn a SwiftUI view hierarchy into a view. You can use these components from other regexes created with the builder syntax, and you can even make them recursive. Regex builders also support dropping some Swift types directly into a regex. For example, string literals just match the exact text inside them -- no special escaping needed. You can also use regex literals in the middle of a regex builder. So you can strike a balance between the clarity of a regex builder and the conciseness of a regex literal. And other types -- like this Foundation date-format style -- can integrate custom parsing logic with regex builders, and even convert the data to a richer type before capturing it. Finally, no matter which syntax you use, regexes support a bunch of useful matching methods and strongly typed captures that are easy to use. Now, for the regex nerds who have been squirming in their seats, Swift Regex uses a brand-new open source matching engine, with a feature set comparable to the most advanced regex implementations. The literal syntax is compatible with the Unicode regex standard, and it has an uncommon level of Unicode correctness. For instance, dot matches a whole character by default, not a Unicode.Scalar or a UTF-8 byte. To use Swift Regex, your app will need to be running on an OS with the Swift Regex engine built into it, like macOS 13 or iOS 16. Swift Regex is an entire language -- well, two languages, really -- so there's much more to say about it. These two sessions -- "Meet Swift Regex" and "Swift Regex: Beyond The Basics" -- will give you lots more details about using it. Finally, there's one place where we took a comprehensive look at the tools we have and made a bunch of changes to improve them. That's in generics and protocols. To show you how these tools have improved, I'll need an example protocol. Let's say you're writing a git client and you have to represent mailmaps in two different ways. When you're displaying commits, you use a type with a dictionary to quickly look up names. But when you're letting users edit the mailmap, you use a type with an array to keep the entries in their original order. And you have a protocol called Mailmap that both of them conform to, so your mailmap parser can add entries to either type. But there are two ways the parser could use the Mailmap protocol. I've written two different versions of this addEntries function to illustrate them, but it's actually kind of hard to explain how they're different, because Swift is using the same syntax for two different things. It turns out that the word "Mailmap" means one thing here but it means something subtly different here.
When you name a protocol in an inheritance list, generic parameter list, generic conformance constraint, or an opaque result type, it means "an instance that conforms to this protocol." But in a variable type, a generic argument, a generic same-type constraint, or a function parameter or result type, it actually means "a box which contains an instance that conforms to this protocol." This distinction is important because the box typically uses more space, takes more time to operate on, and doesn't have all of the capabilities of the instance inside it. But the places where you're using a box look just like the places where you aren't, so it's hard to figure out if you're using one. Swift 5.7 fixes this oversight. When you're using one of these boxes containing a conforming type, Swift will now expect you to write the any keyword. This is not mandatory in code that was valid before Swift 5.7, but it is encouraged and you will see it in generated interfaces and error messages, even if you don't write it out explicitly. So the preferred way to write all of those things in the right-hand column is with the any keyword. If you do that, you'll be able to tell when you're using one of these boxes. Now that the any keyword marks one of the parameters in this example, it's a lot easier to explain the difference between these two functions. addEntries1 takes the Mailmap as a generic type; addEntries2 takes it as an any type. And it's also easier for error messages to explain what's happening when you hit one of the limitations of any types. For instance, this mergeMailmaps function tries to pass an any Mailmap to a generic Mailmap parameter. This used to produce an error saying that Mailmap cannot conform to itself, which always seemed kind of paradoxical. But now that we have the concept of any types, we can explain what's happening more clearly. The problem is that any Mailmap -- the box containing a mailmap -- doesn't conform to the Mailmap protocol. But the box is what you're trying to pass, and it doesn't fit into the generic parameter. If you want to pass the instance inside the box here, you'd have to somehow open up the box, take out the mailmap inside it, and pass that instead. But actually, in simple cases like this one, Swift will now just do that for you. Open up the box, take out the instance inside it, and pass it to the generic parameter. So you won't be seeing this error message nearly as much anymore. But there's an even more exciting improvement to any types than that one. Previously, a protocol could not be used as an any type if it either used the self type or had associated types, or even just conformed to a protocol that did, like Equatable. But in Swift 5.7, this error is just -- poof -- gone. A lot of developers have struggled with this one, so we're thrilled to have fixed it at the source. Now, that's exciting enough just for protocols like Mailmap, but this goes even further. Because even very sophisticated protocols, like Collection, can be used as any types. You can even specify the element type, thanks to a new feature called "primary associated types." A lot of associated types are basically just implementation details. You usually don't care which type a collection uses for its index, iterator, or subsequence; you just need to use the type it supports. But its Element is a different story. You might not always care exactly which Element type a collection uses, but you're probably going to do something with the elements, so you'll need to constrain them or return them or something. When you have an associated type like Element that nearly every user of a protocol will care about, you can put its name after the protocol's name in angle brackets to make it a primary associated type. Once you do that, you can constrain the protocol's primary associated types with the angle bracket syntax pretty much anywhere you can write the protocol's name, including in any Collection. Now, some of you might be looking at this type and going, "Wait a minute. Isn't there already something called AnyCollection, just run together and with the 'any' capitalized?" And you're right, there is! The old AnyCollection is a type-erasing wrapper -- a handwritten struct which serves the same purpose as an any type. The difference is that the AnyCollection struct is just line after line of the most boring boilerplate code you've ever seen in your life; whereas the any type is a built-in language feature that does basically the same thing -- for free! Now, the AnyCollection struct will stick around for backwards compatibility and because it has a couple of features that any types can't quite match yet. But if you have your own type-erasing wrappers in your code, you might want to see if you can reimplement them using built-in any types instead of box classes or closures. Or maybe even just replace them with type aliases. So Swift has dramatically improved any types. It's introduced the any keyword so you can see where you're using them. It allows you to pass them to generic arguments. It's abolished the restriction that kept many protocols from being used with them. And it even lets you constrain an any type's primary associated types. But even with all of those improvements, any types still have limitations. For example, even though you can now use any Mailmaps when Mailmap conforms to Equatable, you still can't use the equals operator with them, because the equals operator requires both mailmaps to have the same concrete type, but that's not guaranteed when you're using two any Mailmaps. So even though Swift has improved any types a lot, they still have important limitations, in both capabilities and performance. And that's why a lot of the time, you shouldn't use them -- you should use generics instead. So let's go back to the two versions of addEntries and apply that wisdom. Both versions do exactly the same thing, but the one on the top uses generic types, and the one on the bottom uses any types. The generic version will likely be more efficient and more capable, so you ought to use that one. And yet, you're probably tempted to use any types, because they're just so much easier to read and write. To write the generic version, you need to declare two generic type names, constrain them both, and finally, use those generic type names as the types of the parameters. That's just exhausting compared to writing "any Collection" and "any Mailmap." So you'd be tempted to use any types despite their drawbacks. But that's the same thing I was talking about earlier -- using your hammer instead of your screwdriver because the hammer has a big, grippy handle. You shouldn't have to make that choice. So Swift is making generics just as easy to use as any types. If a generic parameter is only used in one place, you can now write it with the some keyword as a shorthand. And it even supports primary associated types, so you can accept all collections of mailmap entries with code that's a lot easier to understand. With that in your toolbox, there's no reason to avoid generics anymore. If you have a choice between generics and any types, generics will be just as easy to use -- just write "some" instead of "any". So you might as well use the best tool for the job. I've only scratched the surface of these changes to protocols and generics. For an in-depth look, as well as a great review of all of Swift's generics features, we have two more talks for you this year: "Embrace Swift generics," and "Design protocol interfaces in Swift." Now, Angela and I have talked about nearly two dozen changes to Swift, but there are lots more that we couldn't fit into this session. Every one of these changes was pitched, proposed, reviewed, and accepted publicly in the Evolution board on the Swift Forums. And all of them were shaped and realized with the help of community members from outside Apple. If you're one of those people, thank you for making Swift 5.7 the great release it is. And if you want to help decide what comes next, visit Swift.org/contributing to find out how to participate. Thanks for your time. And happy coding.
♪
-
-
7:19 - Command plugins
@main struct MyPlugin: CommandPlugin { func performCommand(context: PluginContext, arguments: [String]) throws { let process = try Process.run(doccExec, arguments: doccArgs) process.waitUntilExit() } }
-
8:34 - Build tool plugins
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { // Run some command } }
-
8:39 - Implementing a build tool plugin
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources") return [ .buildCommand( displayName: "Running MyTool", executable: try context.tool(named: "mycooltool").path, arguments: ["create"], outputFilesDirectory: generatedSources) ] } }
-
9:23 - Module disambiguation with module aliases
let package = Package( name: "MyStunningApp", dependencies: [ .package(url: "https://.../swift-metrics.git"), .package(url: "https://.../swift-log.git") ], products: [ .executable(name: "MyStunningApp", targets: ["MyStunningApp"]) ], targets: [ .executableTarget( name: "MyStunningApp", dependencies: [ .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics", moduleAliases: ["Logging": "MetricsLogging"]), ])])
-
9:42 - Distinguishing between modules with the same name
// MyStunningApp import Logging // from swift-log import MetricsLogging // from swift-metrics let swiftLogger = Logging.Logger() let metricsLogger = MetricsLogging.Logger()
-
11:09 - Example set of protocols
public protocol NonEmptyProtocol: Collection where Element == C.Element, Index == C.Index { associatedtype C: Collection } public protocol MultiPoint { associatedtype C: CoordinateSystem typealias P = Self.C.P associatedtype X: NonEmptyProtocol where X.C: NonEmptyProtocol, X.Element == Self.P } public protocol CoordinateSystem { associatedtype P: Point where Self.P.C == Self associatedtype S: Size where Self.S.C == Self associatedtype L: Line where Self.L.C == Self associatedtype B: BoundingBox where Self.B.C == Self } public protocol Line: MultiPoint {} public protocol Size { associatedtype C: CoordinateSystem where Self.C.S == Self } public protocol BoundingBox { associatedtype C: CoordinateSystem typealias P = Self.C.P typealias S = Self.C.S } public protocol Point { associatedtype C: CoordinateSystem where Self.C.P == Self }
-
13:14 - Memory safety in Swift
var numbers = [3, 2, 1] numbers.removeAll(where: { number in number == numbers.count })
-
14:10 - Thread safety in Swift
var numbers = [3, 2, 1] Task { numbers.append(0) } numbers.removeLast()
-
15:54 - A distributed actor player and a distributed function
distributed actor Player { var ai: PlayerBotAI? var gameState: GameState distributed func makeMove() -> GameMove { return ai.decideNextMove(given: &gameState) } }
-
16:20 - A distributed actor call
func endOfRound(players: [Player]) async throws { // Have each of the players make their move for player in players { let move = try await player.makeMove() } }
-
20:12 - Optional unwrapping
if let mailmapURL = mailmapURL { mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n") }
-
20:29 - Optional unwrapping with long variable names
if let workingDirectoryMailmapURL = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") }
-
20:35 - Cryptic abbreviated variable names
if let wdmu = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: wdmu).split(separator: "\n") }
-
20:46 - Unwrapping optionals in Swift 5.7
if let workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") } guard let workingDirectoryMailmapURL else { return } mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
-
21:07 - Closure type inference
let entries = mailmapLines.compactMap { line in try? parseLine(line) } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
21:33 - Type inference for complicated closures
let entries = mailmapLines.compactMap { line in do { return try parseLine(line) } catch { logger.warn("Mailmap error: \(error)") return nil } } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
22:15 - Mismatches that are harmless in C...
// Mismatches that are harmless in C… int mailmap_get_size(mailmap_t *map); void mailmap_truncate(mailmap_t *map, unsigned *sizeInOut); void remove_duplicates(mailmap_t *map) { int size = mailmap_get_size(map); size -= move_duplicates_to_end(map); mailmap_truncate(map, &size); } // …cause problems in Swift. func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) mailmap_truncate(map, &size) }
-
22:33 - Better interoperability with C-family code
func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) withUnsafeMutablePointer(to: &size) { signedSizePtr in signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in mailmap_truncate(map, unsignedSizePtr) } } }
-
23:41 - String parsing is hard
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:05 - String parsing is still hard with better indexing
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[(emailEnd 1)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[(nameEnd 1)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:20 - What's the problem?
let line = "Becca Royal-Gordon <[email protected]> # Comment" func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:55 - Drawing a picture
"Becca Royal-Gordon <[email protected]> # Comment" / space name space < email > space # or EOL / / \h* ( [^<#] ? )?? \h* < ( [^>#] ) > \h* (?: #|\Z) /
-
25:10 - Swift Regex using a literal
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/ guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
25:46 - Did a cat walk across your keyboard?
/\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/
-
26:34 - Regex builder
import RegexBuilder let regex = Regex { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } }
-
27:05 - Turn a regex into a reusable component
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } } }
-
27:30 - Use regex literals within a builder
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:39 - Use Date parsers within Regex builders
struct DatedMailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring, Date)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) Capture(.iso8601.year().month().day()) ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:49 - Matching methods and strongly type captures in Regex
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/ // or let regex = MailmapLine() guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
29:02 - A use case for protocols
/// Used in the commit list UI struct HashedMailmap { var replacementNames: [String: String] = [:] } /// Used in the mailmap editor UI struct OrderedMailmap { var entries: [MailmapEntry] = [] } protocol Mailmap { mutating func addEntry(_ entry: MailmapEntry) } extension HashedMailmap: Mailmap { … } extension OrderedMailmap: Mailmap { … }
-
29:26 - Using the Mailmap protocol
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:05 - `Mailmap` and `any Mailmap`
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:17 - Improvements to `any` types
extension Mailmap { mutating func mergeEntries<Other: Mailmap>(from other: Other) { … } } func mergeMailmaps(_ a: any Mailmap, _ b: any Mailmap) -> any Mailmap { var copy = a copy.mergeEntries(from: b) return a }
-
32:21 - More improvements to `any` types
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
32:54 - Using Collection as an `any` type
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
33:04 - Primary associated types
protocol Collection<Element>: Sequence { associatedtype Index: Comparable associatedtype Iterator: IteratorProtocol<Element> associatedtype SubSequence: Collection<Element> where SubSequence.Index == Index, SubSequence.SubSequence == SubSequence associatedtype Element }
-
33:42 - Using primary associated types in Collection
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } } extension Collection<MailmapEntry> { … }
-
34:35 - Example of type erasing wrappers
struct AnySprocket: Sprocket { private class Base { … } private class Box<S: Sprocket>: Base { … } private var box: Base // …dozens of lines of code you hate // having to maintain… }
-
34:38 - Replace boxes with built-in `any` types
struct AnySprocket: Sprocket { private var box: any Sprocket // …fewer lines of code you hate // having to maintain… }
-
34:44 - Or try type aliases
typealias AnySprocket = any Sprocket
-
35:09 - `any` types have important limitations
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func areMailmapsIdentical(_ a: any Mailmap, _ b: any Mailmap) -> Bool { return a == b }
-
35:44 - Using generic types vs. `any` types
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:40 - `some Mailmap` and `any Mailmap`
func addEntries1<Entries: Collection<MailmapEntry>>(_ entries: Entries, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:50 - `some Mailmap` and `any Mailmap` with Collection and primary associated types
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.