Swift - Overview
Cory Benfield: 2014, at Apple's Worldwide Developer Conference, we introduced Swift to the world. At the time, Craig Federighi described Swift as fast, modern, designed for safety, and enabling a high level of interactivity and development. As a high-level description, Swift is an ahead-of-time compiled, memory-safe, multi-paradigm programming language. Swift uses reference counting for memory management instead of garbage collection, and has recently added support for limited lifetime analysis. Finally, Swift leans heavily on types with value semantics by leveraging copy-on-write. This enables powerful local reasoning, as value types are either mutable or shared, but not both. Most of you will be familiar with Swift as an application programming language, principally used on Apple's platforms. When Swift was first introduced, this is what it was introduced to be, and it remains a major use of the language today. This is a natural instinct. However, people inside and outside of Apple have been using Swift to run services for more than 8 years.
The Vapor web framework, written in Swift, was started in January of 2016. At Apple, we've written all kinds of services in Swift, from iCloud Keychain to App Store processing pipelines and SharePlay file sharing, and most recently, Private Cloud Compute, one of our more prominent and innovative services, is also written in Swift. Private Cloud Compute allows Apple Intelligence to flex and scale its computational capacity and draw on even larger server-based models for more complex requests, while protecting user privacy. These models run on servers we have especially created using Apple Silicon. These Apple Silicon servers offer the privacy and security of your iPhone from the silicon on up, draw on the security properties of the Swift programming language, and run software with transparency built in.
Architecturally, Private Cloud Compute will look like a service familiar to many of you. There are load balancers fronting a fleet of Apple Silicon machines that run the inference service. A number of supporting services help manage the deployment, including a service discovery service, a control plane, a transparency service, and a few miscellaneous others. Private Cloud Compute differs from your typical service because of its threat model. At Apple, user privacy is a core principle for us. As a result, running inference for large language models is extremely sensitive territory. We needed to build a service that users could trust and verify. The result is that this service is divided into two parts.
One part are the untrusted components. These components are cryptographically prevented from being able to see any of the user data. The other part are the components that can see user data, the trusted components. Everything on the trusted side needs to be something that can be verified from the silicon up. This forced us to think hard about the tools for the job when building trusted components. To achieve their trustworthiness, they needed to be built on top of Apple Silicon to act as a hardware trust anchor. They needed to be built on top of our secure boot infrastructure. They needed to be fully verifiable at runtime. All of this while also offering minimal attack surface and enabling us to actually build and operate this service. As I'm sure you can tell at this point, we chose Swift for the trusted components.
Roadmap
What are some of the reasons that led us there? Let's explore them together. I'll start by giving a quick introduction to Swift and some of its core language features. Then we'll talk about what features of Swift make it a great choice for writing services. We'll explore how those features apply to Private Cloud Compute. Then we'll wrap up by looking at some principles for adopting Swift in your services.
Introduction to Swift
First, an introduction to Swift. Swift's an imperative programming language that focuses on low ceremony and progressive disclosure. Another way to put that is that in Swift, simple things should be simple. Swift ships with a small standard library that contains a number of fundamental data types, such as strings, arrays, dictionaries, and sets. Swift also splits the world into reference and value types. Swift's principal reference type is the class. Classes are reference types because each value corresponding to a class, points to a single backing location, often, but not always, heap-allocated. When they are implicitly copied, like they are here, they form another reference to the backing storage. Thus, modifying through one reference causes a modification to all other references to the backing class.
Classes enable traditional object-oriented programming patterns. They support inheritance, dynamic dispatch, and all of the other features of class types in many other programming languages you're already familiar with. Classes have their lifetime managed via automatic reference counting. When the person object is constructed here, backing storage is heap-allocated, and a reference is made to that backing storage. When the reference is copied on the next line, another reference to that backing storage is created, and the reference count is atomically incremented. Swift also has value types. To provide a quick definition, a value type is one in which each copy of a value is independent of every other copy of a value.
A good example of a type that most programmers expect to behave this way is an integer. The print result on the slide here, I think, is something that is in line with everybody's expectations. However, this applies to several other types in Swift as well, for example, Swift structures. These are aggregate or product types, and they behave just like integers do. Each copy is independent of all the others, and modifications to one copy don't affect any other copy. It also applies to Swift's powerful enumeration types. These are tagged unions or sum types. Here again, each copy of the value is independent from every other copy. We can also see here Swift support for keywords expressing ownership and mutability semantics. Here, the mutating keyword indicates that this method can only be called on a value that is uniquely owned and mutable, and that this method intends to mutate that value. Swift takes its focus on value semantics further than many languages by also extending them to collections.
In Swift, all the standard library collections and very nearly all collections in the library ecosystem are also value types. This is achieved by leveraging copy-on-write, making shallow copies cheap unless or until they are modified. This feature enables powerful local reasoning and is one of Swift's really valuable language features. While reference types are available and useful, Swift tends to reward programmers for adopting and embracing value semantics, which removes spooky action at a distance and makes code simpler and easier to understand. Swift's value semantics are powerful for making code understandable, which is great for services.
Swift Features for Services
Let's talk about a few other great language features that Swift has for writing services. First, let me preach to the choir a little. Swift's memory safe. Memory safety brings huge benefits to the table for services. Most importantly, services by their very nature are exposed to vast amounts of untrusted traffic over the network. When we bring network protocol implementations and parsers into memory safe code, we massively reduce exploitable bug density in our services. Swift is memory safe by default, enabling us to write complex byte manipulations without needing to write a single line of unsafe code. Users can still drop to unsafe code when it's necessary to do so, but we can build safe abstractions on top of these layers to make interacting with the system easier and safer. This lets us write this kind of low-level manipulation code. This code sample is taken directly from our implementation of the HTTP/2 network protocol, and it implements the variable width encoding from the HTTP/2 header compression scheme. Swift also makes it easy to drop down to that unsafe code when you need to.
For example, Swift strings can easily offer you a view that is compatible with APIs that expect C strings. In some cases, Swift will understand what you mean and emit the necessary transformation automatically. For example, we can rewrite this code to this. Here, Swift automatically translates the path to a C string before it calls the libc function. However, unlike many traditional services languages, such as Python, Ruby, or Go, Swift is a natively compiled language without a garbage collector. It's also designed from the ground up to be frugal with memory, a result of its heritage as a language for Apple's mobile devices. The result is that Swift services frequently use vastly less memory than their virtual machine-based counterparts. I can give an example of this kind of change. Last year, we moved a service from Java to Swift. This particular service handles a very high request rate and is extremely latency sensitive, with P99 latency targets below a millisecond. The service is fairly stateless.
Those of you with experience in Java development may recognize this service profile as something that will benefit massively from having a heap size much larger than the working set. This will minimize the number of objects that escape from YoungGen or Eden Space and keep the sweep phase of garbage collection fast and reduce the frequency of stop-the-world garbage collector events. In this case, this service was deployed with 32 gigabyte heaps. This is more than three orders of magnitude larger than the actual working set required by the service. The density of this service deployment was limited much more by the memory required for the heap than by the CPU required for the load profile. When the service was rebuilt into Swift, the heap requirement dropped to below 256 megabytes. This is much closer to the actual working set size of the service. Those of you who have done rewrites of applications from garbage-collected languages to non-garbage collected ones will be familiar with stories of this kind.
In my experience, Swift is even more frugal with memory than its peers. This frugality allows us to make much better use of hardware. The service I discussed here was able to reduce its hardware requirements by 40%, which is more than the CPU improvement it saw from the rewrite, but less than that RAM improvement I showed you. This is an indication that the achievable deployment density for this service moved from being principally RAM-limited to being principally CPU-limited. This is a really common pattern in cloud services. We often see services with bursty CPU utilization, but fairly constant memory utilization. The result is that when we shrink the size of the needed memory set, we're able to more tightly pack processes onto the same nodes, achieving much improved utilization of the silicon.
Performance is another great asset of Swift. When we think about performance in services, we tend to think about either or both of latency and throughput. Different services care more about one than the other, but it's the rare service that is entirely insensitive to both of these. Swift provides great benefits for both. Let's tackle latency first. Latency is a critical metric for user interactive workloads. Here I am defining latency quite broadly as the perception of delay by a user. In a web context, this would be roughly equivalent to the Largest Contentful Paint, a Core Web Vital. There's a substantial catalog of research on the impact of high latency on user experience that I'm not going to revisit today. The important takeaway is that low latency is vital for user happiness. A particular corollary of this is that low tail latencies are important. This is because most users don't interact with your service only once in a session. Instead, they tend to do so tens or hundreds of times.
If your P50 latency is great, but your P99 latency is diabolical, and your average user interacts with your service 20 times a session, one session in five sees poor latencies on at least one interaction. Users notice and hate this. Swift's lack of a garbage collector eliminates the most common cause of high tail latencies, garbage collector spikes. We don't want to pretend that this isn't a problem that can be managed. There are many great engineers who can help mitigate garbage collector spikes. That specific, detailed knowledge that can take a lifetime to obtain, it's a lot easier to just use a programming language that doesn't need you to do that tuning at all.
As for base case latency, Swift's answer is the same as with throughput, so we can talk about that next. The first performance asset for Swift is that it's built on top of LLVM, giving it access to the many decades of optimizer research that has gone into the LLVM backends. This ensures that Swift is at least as good as any other LLVM-based language at common tasks, such as integer manipulation. Writing Swift code that does low-level mathematical operations is fast and powerful. For example, here is the canonical definition of the ChaCha20 stream cipher quarter round, the building block of ChaCha20, written in Swift. This code is almost identical to what you would write in C, with a few additional helpers for custom operators and rotations. When compiled and optimized, that Swift code produces this assembly. This assembly is identical to the assembly that the equivalent C implementation produces down to the instruction.
The important thing to take away from this, if you aren't familiar with ARM64 assembly, is that this is straight line, branchless, and short. This powerful LLVM support enables the basis of Swift's performance story, but it's not where it stops.
Swift also enables zero cost abstractions. I showed this code earlier as an example of Swift's memory safety, but it's also a powerful example of the ability of Swift to provide zero cost abstractions. These operations on the bytes variable are operations on a type called ByteBuffer view. This type is a highly abstracted representation of a buffer of bytes provided by our high-performance networking stack. The subscripting and indexing operations are all enforcing safety and bounds checking, preventing any out-of-bounds access. These operations, with reporting overflow in their name, provide the ability to not just perform overflow-checked arithmetic, but to branch on the overflow flag from the CPU. While it's highly abstracted, this code compiles down to a tight optimal core loop, which is this.
The Swift compiler, in this case, has actually observed the loop can be unrolled as it won't execute more than eight times, so I've only reproduced one of the unrolled blocks. Here, the compiler has calculated the shift that would be applied at this loop iteration and hardcoded it. It's also hardcoded and inlined the masks. We mask out all but the top bit on the first instruction, and then for the last one, we exit the loop if the top bit was not set. The compiler has also broken the data dependency on calculating the offset into the buffer. Instead of just adding one to the prior offset indexing register, the compiler has derived this from a fixed calculation on the base index, which makes it easier for the processor to execute instructions concurrently.
Finally, I want to call your attention to one other data point, the overflow checks are gone. We had these reporting overflowed functions in the code earlier. These are needed to defend against integer overflow for the case where we have a large number of bytes making up our integer. Because the loop has been unrolled, the compiler's been able to observe that these overflow checks aren't needed at all at this state of the computation because we cannot possibly have overflowed, and it has elided them. The last two loop iterations do contain jumps to the overflow handling case when needed. This is a great example of the power of zero cost abstractions. We could write code that was defensive of edge cases and then let the compiler prove that those edge cases cannot be hit. While with defaults to letting users just code, in cases when you're writing performance-sensitive code, you can seamlessly start using more constrained features to unlock more performance.
For example, by default, all Swift types are copyable, but from Swift 5.9, Swift types can declare themselves as non-implicitly copyable, suppressing their default copy constructors and enabling unique ownership tracking. The language also grew new keywords to explicitly consume values. Together, these two features can reduce copies and remove reference counting operations in hot code paths by making explicit how ownership of a value changes through the lifetime of the code. This works even with copyable values, such as Swift's regular array types.
Another example is that many Swift types have quite complex copy operations. This can make some generic code larger or more complex than it needs to be. However, if the performance and code size of these operations matters, programmers can constrain their generic types to be bitwise copyable, minimizing overhead. In the example here, the function toSingleElementArray would normally have to call the element's copy constructor. Here, by constraining the element type to bitwise copyable, we know the element must safely be able to be copied via a straight call to memcpy, so that direct copy can be inserted instead.
Swift's Interoperability
One of Swift's real superpowers is interoperability between languages. From its earliest days, Swift was designed with interoperability in mind. As an example, we can take a look at how Swift interoperates with C and C++, how to wrap those languages into safe abstractions, and then finally, how we can bring all of that together and interoperate with a safe language, like Java. First, we'll look at C. C is pervasive, and on basically all platforms has a clear and well-defined ABI. This makes it an attractive target for low ceremony interop, and Swift delivered on this from the very start. Importing C headers and working with C definitions in Swift is straightforward.
Consider, for example, sqlite3. Here's a small sample of the API surface of sqlite3, containing opaque type definitions, macros, pointers, and function pointers. Those who are familiar with C will recognize this as a small, well-factored C API. How do we call this from Swift? To import C headers, what we need to do is define a Clang module for them. Clang modules are a feature of the Clang compiler and enable the Clang compiler to understand which header files and libraries define which interfaces. An example of the Clang module we might write for sqlite3 is here. The module declares the relevant header, exports all the defined symbols, and instructs Clang and Swift to link the sqlite3 library when they import this module. Then, from Swift, all we need to do is import this module, and we're good to go.
It's worth stopping here and noting how much of the C API came through straightforwardly. These C functions, for example, came through cleanly. We can just call them as if they were Swift functions. In fact, these calls have no overhead whatsoever. Swift can call these C functions as cheaply as if it were C. This zero overhead abstraction makes C interop a really powerful tool and gives Swift access to the existing wide world of deployed C code without any costs. This macro definition from the header has come through, allowing us to comfortably use it from Swift just as from C, despite the fact that Swift does not have C macros. We can even declare inline closures in Swift, which are turned into functions that can be used as function pointers.
Here, this closure turns around and calls another Swift function, but I could have inlined the method body of the print row function into this part of the code, and that would have worked just fine. This is extremely powerful. With no overhead, we've gotten access to a complete C library to call from Swift. The ease of interoperability with C is great for services. We're able to get started by reusing existing server libraries written in C, which almost all major features and in services have. These can be used to bootstrap an integration while leaving a great hook to rewrite that integration into Swift later when it's convenient. In most operating systems, the C ABI remains the lingua franca for accessing operating system and platform features. This enables Swift to easily access those features.
What about a more complex language? What about C++? Swift learned to interoperate with C++ a little more recently. Like the C layer, this was built deeply into the compiler, and it taught Swift a few new tricks. Naturally, all of the interop from C remains. We can call functions and use structs. Now, with C++ interoperability, Swift understands C++'s army of constructors and can use them when moving values around. Swift can unfold and understand instantiated templates. C++ types can be conformed to Swift protocols. Swift can even understand C++ containers as if they were Swift collection types.
As an example, we can consider a popular C++ library, simdjson. Again, if we define a module map for it, we can simply import it by name and use it directly from Swift. This is a pretty dense code sample, but we're using a bunch of interop features here. First, observe that we're taking a C++ type, std.string_view, and conforming it to a Swift protocol, CxxSequence. This is a helper protocol provided by the Swift standard library that lets us turn the string view into a Swift sequence that we can iterate.
In this extension, we use that conformance to construct a Swift string from a C++ std.string_view. I've omitted the body of that code because we do need to insert a few copies, but there is some type conversion there that makes that easier. What's important is that we are extending Swift's string type in order to make it easy to construct those Swift strings from C++ string views. We can then interleave Swift and C++ code to write the parser. This is a nearly straight translation of the C++ code that you would write to do this job in that language. We create a simdjson padded string from the contents of a file. We then construct to the parser and iterate the content of the file to set up the context. This is my favorite line in this example. You can see here that Swift understands C++'s subscript operator and turns it into the appropriate subscript operation in Swift. It also silently translates our Swift string to a std.string rvalue reference. This lets us write very natural code to perform this subscripting. It's extremely freeing to be able to access a C++ library with little to no translation overhead. Interoperating with C++ gives us many of the same benefits as interoperating with C, but a few extra come along.
First, C++ has been a common choice for some time when it comes to writing performance-focused libraries. simdjson is a good example here, but there are many others produced by a wide range of teams across various ecosystems. Getting access to those libraries easily can unlock some powerful capabilities. Importantly, C++ is also a far more common choice than C for writing large services. This makes it a tempting target for incremental rewrites, as there are many more large-scale services written in C++ that can benefit from an incremental rewrite into a safe language.
While both C and C++ examples show us how we can get access to the wide world of C and C++ libraries, they both had some downsides. The biggest is that they produced non-idiomatic code, which makes it harder for Swift programmers to access and use those libraries comfortably. They also cause programmers to need to interact with unsafe code, which, when mishandled, can introduce safety issues into our code.
As a result, it's a good practice to wrap these libraries in safe interfaces. Swift makes this easy to do, so let's do a quick worked example. Let's consider, again, an ideal SQLite API. That might look like this. Here, the user can construct the database object in some own safe wrapper, then write SQL query strings to issue database queries. Then they get the rows back as a collection. Here's how we could build that in Swift. First, we need to define a Swift type to hold the underlying database pointer. This type needs to be something that has a defined lifetime and can be cleaned up when it's done.
In Swift, this means either a class or a non-copyable struct. For ease of use, we'll pick a class for now. This class will hold the pointer to the SQLite database. Now, we need to create the pointer. We can move the code from earlier into the constructor of our new SQLite database class. Notice here that we can encode an invariant into the Swift code that's only present in C in the documentation. SQLite's documentation claims that there will be a database pointer returned if the return code is SQLITE_OK.
We can, therefore, check in the constructor for SQLITE_OK, and if it's present, unwrap the optional pointer into a concrete non-optional one. We now know that at all times during the lifetime of this class, this pointer will not be null, and we cannot dereference a null pointer here. This is only half the job, though. We also need to make sure that when our SQLite database object dies, we tear down the underlying database as well. We bring the cleanup function previously in a defer statement into the deinit of this class. deinit in Swift is not a finalizer. It will execute immediately upon the release of the last reference to the class. It's a great way to clean up this kind of handle. Here we have a nice memory safe wrapper of the sqlite3 database type in just a few lines of Swift.
Using a class like this is the most convenient way to wrap the type up. However, Swift classes are usually heap allocated. This means this solution can incur a few small overheads in the Swift code. We would need to pay for reference counting costs. We would pay for heap allocation and a free. We would pay for a double pointer indirection. In most code, these costs don't matter, and so a class is the correct choice.
If we didn't want to pay for them, Swift has an answer for us. We can instead turn the SQLite database into a structure and make it a non-implicitly copyable type. This involves suppressing the copyable conformance using this tilde-based syntax used in this context. This tells Swift that the SQLite database type does not have an implicit copy constructor and so the Swift language cannot insert copies. This means that any value of this type still has a definite lifetime and we can continue to keep our deinit to clean things up. This is an advanced construction and most Swift code won't bother with it. Usually, the costs are not worth the extra inconvenience of using a type that cannot be implicitly copied around. However, for cases where the extra performance is necessary, this is available.
What's missing in this example, though, is any actual execution function. Let's turn our attention to that. What we want is a function that looks like this. Execute will take a SQL query as a string and return the SQL representations of each of the columns for each of the rows as an array of arrays of strings. This is what our previous code for executing the query looked like. We need to transform it to fit our new representation. Some of this transformation is fairly clear. For example, we can use our database pointer reference. We can also replace the string literal with the SQL query string we get given from the user. Notice here that we have not had to tell Swift how to convert the Swift string into a C string. Swift transparently knows how to do that. Now we hit a little snag.
Previously, we just unilaterally printed the output. We didn't need to deal with the question of how to get the column data out from the callback, but now we do. Recall that this is the definition of the sqlite3_exec function. Those of you who have written a lot of C will note that this follows a common convention for C callbacks, where the user can provide a user data object as a void pointer, which will be passed directly through to the callback invocation, which you can see here.
In this case, what we need is something we can represent as a pointer that we can pass through this interface. Swift offers quite a few ways to do this, but for this example, we'll take advantage of the fact that classes are always represented by a pointer to their backing storage. Returning to our code, we can define a class to collect our results. It needs to have somewhere to store the rows, so we add that as a stored field. We then implement the row insertion function. This will be called from our callback. It takes the count of columns and the pointer to their descriptions. It wraps them into a buffer pointer, which provides Swift collection semantics on top of a pointer and a length. Then it turns those C strings into Swift strings and appends the result. This little block here is a really nice example of the way that Swift allows zero ceremony interaction between C and Swift.
We can move that aside and bring our SQLite code back. We can now use our row gatherer class to start gathering the rows. We construct it, and then we need to pass it into the closure. To do that, we use a special Swift standard library type called unmanaged, which lets us take manual control of the reference counting. Here, we pass our row gatherer unretained and erase it to an opaque pointer. When we pass the row gatherer unretained, we're telling Swift not to issue an extra retain call. This is fine here, because we're holding a reference to the row gatherer ourselves in Swift code. That reference will outlive the reference we're passing to C, and so we don't need an extra retain.
Next, we need to replace this callback. We're again using Swift's neat feature, where we can use the inline closure syntax to declare the function pointer that C will actually call. Here again, we use unmanaged, this time to reverse our prior pattern. We unwrap the opaque pointer and then take it unretained. This matches the prior call and ensures that our reference counting lines up. We can then call the insert row function with the relevant fields from the C callback, and then we just need to extract the results. This is a complete implementation of the safe wrapper around the SQLite database. Just to show what that new usage site looks like, it looks exactly like what we wanted it to look like in the first place. All of our unsafe code is gone, and we have a nice wrapper that we can use in Swift. Extending this would be fairly straightforward. For example, we might want to do row streaming or offer types for the underlying columns.
What about interop with safe languages, though? For interop with safe languages, Swift can rely on targeted language feature addition combined with helpful libraries. For example, in the case of Java, macros and C interop can be leveraged to make bidirectional operability between Java and Swift possible. This lets the interface layer make sure that it enforces the safety guarantees of both languages rather than having users manually write wrappers. It's worth stopping and asking why you use Java as an example here. There is a lot of Java in the world, in many deployed services. We are pragmatists. We don't expect that Java to be rewritten, nor do we expect everybody to stop writing new Java. Instead, we want users to be able to take advantage of work in both languages.
In Swift, we have a number of libraries that we at Apple have been thrilled to be able to reuse from Java to minimize the scope of our dependencies. For example, we've used Swift crypto for cryptographic operations and high-level primitives, such as the Anonymous Rate-Limited Credential draft specification my colleagues Kathy and Chris are working on at the IETF. However, I want to make clear that Java interoperability in Swift is a work in progress. What I'm going to discuss is going to change substantially over the next few months. Java interoperability in Swift is made up of two parts. The first is hosting a Java library inside a Swift application. This capability is built on top of JNI, relying on the JNI interface to expose the Java methods into Swift code. The second is hosting a Swift library inside a Java application. This capability is built on top of the new JEP 454 Foreign Function and Memory interface, or FFM.
In some ways, this second example is actually a little more boring than the first one, so let's tackle these in reverse order. Calling Swift from Java relies on a tool called jextract-swift. This tool is very similar to the jextract tool that ships with Java, but instead of relying on C header files, it takes advantage of the .swiftinterface file. You can understand the .swiftinterface file as being very similar in spirit to a header file, but instead it contains .swiftinterface declarations. The analysis that jextract-swift has to do is quite in-depth, because Swift types can have extensions provided from all kinds of locations. Once it's complete, the tool can emit Java sources. These Java sources use the Java FFM API that I just mentioned to call Swift methods. Additionally, the tool will generate a few Swift thunks to make possible some interesting manipulations of Swift values.
As an example of the result, we can consider this Swift class, which is not a very complex thing. When run through jextract-swift, the tool produces a Java class with a very similar interface. Swift classes come across into Java in the form of a wrapper class. jextract-swift can bring over value types as well. Here we bring over a Swift structure. This also ends up as a Java class, but this Java class understands the Swift copy-on-write pattern. Note that here we were even able to observe the wrapper class for the other type that we just defined and make sure that the types line up. In Java, we pass the wrapper class, which is unwrapped and passed to Swift as the underlying Swift class. This tool lets us easily and transparently wrap an entire Swift API into a Java API. At the time of giving this presentation, there are still some features missing here, but the core functionality works really nicely.
What about the other direction? I teased that this was a little more interesting, so how can we call Java libraries from Swift? For this, we introduce a tool called Java2Swift. This tool takes a config file and a pile of Java bytecode objects. Those objects may be in the form of a jar, and in that case, the config file can be generated by the Java2Swift tool as well. When the tool is run as part of a build, the tool spits out a number of generated Swift files. These files contain what are essentially wrapper classes, one for each Java class in the module. These definitions let you use the types directly from Swift, but they mostly hide the actual implementation. The real implementation comes from macros. Swift macros are very powerful. For more details on how Swift macros work, I recommend checking out Swift Evolution Proposal 389, which covers attached macros, the kind we have here. For now, you can understand them as being able to modify or entirely replace the content of the declaration to which they are attached.
The Java class macro at the top adds a bunch of machinery for the type itself, and each Java method changes the implementation to perform the relevant call. What's it like to use this wrapper class? We have this code for our generated wrapper, and it turns out that using it feels exactly like using a regular Swift class. We can construct the class using its constructor and then call its methods directly from Swift without ceremony. This is a really pleasant way to work with Java types from Swift, but how do those macros actually work? To find out, we can take a quick look at the Java method macro. This is a small section of its implementation. This is dense code, so I want us to principally focus on the return statement here. I'll split it into lines to make it a little neater.
Ultimately, what this macro is doing is synthesizing a call to a library function. That library function is called either dynamic Java method call or dynamic Java static method call. Depending on whether the macro is called Java static method or not, that library method will do the complex work of actually calling the underlying Java method using JNI. Similar patterns are present for the other macros, adding extra definitions or tweaking the implementations to delegate to the runtime support library.
Java interoperability in Swift is really promising. While it's in its early stages, I'm really happy with how it works so far, and it's another powerful capability for Swift in services. There is a huge amount of Java already written for services, and it will continue to be written. Having a good interoperability story here offers Swift the capacity to be a natural fit for extending Java wherever native code is required, either for access to platform features or for performance reasons.
Similarly, having access to naturally calling Java in Swift makes it easy for Swift to access the Java ecosystem when it's necessary and offers a path towards incremental rewrites where Swift forms the shell rather than forcing a rewrite of the core business logic first. Swift's tools for interoperability are fantastic, and they make it really easy to combine Swift with other languages. This is especially valuable for writing services where we have an enormous variety of languages in use and a vast amount of existing deployed code. Most service development is not green field, and so being able to interoperate gracefully is vital for languages like Swift.
Recap - Features of Swift
Let's recap the cool features of Swift we discussed. We talked about Swift's memory safety, a critical feature of any programming language in 2025. We talked about Swift's efficient use of memory and how it enables you to get more out of your resources. We talked about Swift's powerful tools for getting great performance from your code. We talked about interoperability, Swift's superpower.
Swift in Private Cloud Compute
Having discussed these benefits at a high level, let's take an example of a prominent Swift service we launched recently, Private Cloud Compute. First, as we began with, and as is the theme of the track, memory safety. A major risk to all services are security holes. The privacy guarantees of any service are weakened if threat actors can exploit the network-facing components of the system to gain a foothold. While Private Cloud Compute has extensive defense-in-depth measures that aim to mitigate such a compromise, it's better to never allow an attacker that opportunity to begin with. For Private Cloud Compute, we were able to leverage our substantial existing investment in memory safe Swift protocol implementations. Using SwiftNIO and gRPC Swift throughout the stack, we could minimize the amount of unsafe code parsing untrusted data, shrinking our attack surface.
The same policy was applied throughout the codebase. Speaking of gRPC Swift, another advantage of Swift was our ability to leverage the investment in cloud-native technologies in the Swift ecosystem. Tools like gRPC Swift made it straightforward for us to integrate the trusted components with the untrusted ones using a common interface. Even in a service like Private Cloud Compute, an enormous number of components necessarily rely on cloud-native technologies to get their work done. The Swift ecosystem has a rich collection of implementations for these technologies, and we were able to leverage them for Private Cloud Compute.
The next big win was interoperability. To achieve the privacy goals we had for this service, we needed access to many of the specialized features of Apple Silicon and Apple's operating systems. That meant we needed interoperability with C, Objective-C, and C++. This easy interoperability gave us access to important platform features, such as the Secure Enclave. Importantly, our existing portfolio of libraries for building Swift services, such as SwiftNIO, already ran great on Apple's operating systems, and they had all the appropriate extension points to slot in things like the Secure Enclave. This ease of integration extended all the way through to our inference components, which leveraged Apple's Metal APIs for driving the GPUs for inference.
Speaking of inference, let's quickly talk about memory usage. Large language models are notoriously memory-intensive. While Apple Silicon machines have plenty of low-latency memory available for the CPU and the GPU, it was very important to us that we didn't use more memory than needed in any component that was not actively performing the inference operation. The more memory we could preserve for that work, the more effective the inferencing could be. Again, Swift's frugality with memory made a great asset here. We were able to keep the support components, including network decryption, down to a few hundred megabytes of memory usage in total, freeing almost all of the system memory for use with inferencing. Our use of Swift in Private Cloud Compute has been a huge success. The Swift components have been incredibly reliable, and they've performed fantastically well. While Private Cloud Compute has some unique needs that make Swift an unusually good fit, Swift is a massive part of the way we're building services going forwards.
Principles for Adopting Swift at Apple
With that in mind, let me share some of the principles that we recommend to services teams at Apple when they're considering Swift and that we used when deciding to use Swift in Private Cloud Compute. The first is incremental adoption. Everybody here, Apple included, has got lots of existing deployed software. That software is not going away overnight, or in the next 5 years, or in the next 10. The road towards a fully memory-safe server ecosystem is the road we're traveling, but it is a long way from here to there. This is where Swift's interoperability comes to the fore. We recommend using Swift incrementally as new use cases become available.
Sometimes this is a new component in a microservice architecture which can be interconnected with the rest of the services using technologies like gRPC or OpenAPI. Sometimes this is a new library with a well-defined API boundary and separate responsibilities. Sometimes this is a new tool or utility that you can add to your existing portfolio to enable interacting with your software in a new way, such as a command line interface or a monitoring service.
Other times, this is about replacing an older component. Perhaps taking a critical parsing layer in a C++ server that's showing its age and replacing it with a Swift equivalent. Or out-of-date dependencies, or even entire technologies that are being replaced, perhaps with a move towards something more modern, such as OpenTelemetry. Or maybe layers of your tech stack aren't performing as well as you need them to be, and you need a rewrite to get them to where you want them. Perhaps most often, this ends up being about efficiently implementing new features. Because Swift plays so nicely with others, Swift can be dropped into almost any service that you already have with minimal bridging. We've taken advantage of this capability in services written in Java, in Python, and in C++ in the past 5 years in order to avoid the need to implement a feature multiple times. We expect to continue to do so.
Our Swift services also pull in features that were written in other languages initially, giving us time to start moving incrementally. Sometimes we need to put a layer in front of an existing service to enable some new mode of interaction with it, which gives us another opportunity to breathe life into an existing feature. In all these cases, these bindings enable us to preserve or improve the memory safety story of all of these components.
The next recommendation is to keep an eye out for places where Swift will deliver the most value. These will be places where Swift can bring something the existing stack is not well suited to do. Sometimes these will be great libraries. Sometimes it will be low memory usage. Sometimes it will be performance. As an example, we've seen adopters using Swift where they are working with AWS Lambda or similar technologies. Here they're leveraging the zero startup time and great performance characteristics to minimize their spend and to scale to zero more effectively.
The final major reason to choose Swift for a project is just because you want to. It's easy to forget for those of us who write code for a living, but most of us got into this industry because we enjoyed writing code. Swift is a language that leans into that joy. Its focus on low ceremony means you can write the code you want to write instead of the code you have to write. When you eventually do need to break out the big guns for maximum performance, Swift will smoothly let you adopt those tools and transition to that space. If you want unique ownership and defined lifetimes, you can have them. By default, you just don't need to.
How to Get Started
We've discussed a lot of the features that we think make Swift a great choice for writing services, both at Apple and outside it. If you're curious, what do your next steps look like? It's straightforward to get started with Swift. Swift.org has installers and install instructions for supported platforms. For those of you who like containerization for their development tools, you can grab container images.
If you're a devcontainers kind of person, playing in devcontainers in editors like Visual Studio code can be an extremely valuable way to get started. You could consider giving Java Interop a go. It's a work in progress, but we'd love to find out how it works for you and maybe even get you helping out with bringing it to fully operational status. The best way to find out what Swift might be able to do for you is to just try building something. Maybe a little gRPC service using gRPC Swift or a client that can hit a gRPC endpoint you already have. Or something that interacts with one of the many APIs that offer OpenAPI specifications. No matter what you do, remember to have fun doing it.
See more presentations with transcripts