2026-03-06 • 41:31
Learn how to build memory-safe software and eliminate vulnerability classes by writing security-critical code in Swift. Explore how Swift guarantees safety across bounds, lifetimes, types, initialization, and concurrency. Discover high-performance primitives like Span and non-copyable types, find out how to audit unsafe constructs with Strict Memory Safety, and walk through incrementally migrating existing C modules.
This session was originally presented as part of the Meet with Apple event “Fortify your app: Essential strategies to strengthen security.” Watch the full video for more insights and related sessions.
Speakers: Doug Gregor, Félix Cloutier
Part of: Fortify your app: Essential strategies to strengthen security
Downloads from Apple
Transcript
So I’m Doug from the Swift Language team, and I’m here with my colleague Felix to talk about memory, safety and Swift. So memory safety is a major security challenge. Now, other presentations have talked about mitigations that help find memory safety bugs in C and C++ code bases, or prevent them from becoming security problems. But none of these is as comprehensive or effective as just defining a way memory safety issues in the programming language itself. And so we’re going to start by reviewing how the design of Swift addresses memory safety.
Then we’re going to talk about performance and how to use recently introduced Swift features to get the best performance out of low level code without compromising on safety. Now, this transition to memory safe languages is going to take a very long time. So we’re going to talk through best practices for encapsulating unsafe code and how to incrementally migrate from the C family of languages toward Swift.
Now, first of all, memory safety isn’t some blanket notion about preventing all errors. So good language design can define a way some classes of errors or make them harder to introduce in your code. But the reality is programming errors do happen. So memory safety is about making sure that the program behaves as it was written.
Now that sounds ridiculous on its face. Of course, the program behaves as it was written, but as we’ve talked about earlier, when an attacker exploits a memory safety bug, they can force the program to do something that wasn’t written in the code. That’s why memory safety is such a big part of the security story. Because a memory safety bug anywhere, say, in your C and C++ code can make the program do almost anything.
Now, a memory safe language protects against this kind of absurdity. Now it can do so in either two ways, so it can refuse to compile any code that might contain a memory safety problem, or it can insert checking at runtime to detect when something has gone wrong. You can combine these strategies, and in fact, Swift uses a mixture of compile time and runtime checking that I’ll talk about in a few moments. There’s only one rule here, which is that if a potential memory safety problem occurs, the program must not continue. Trying to continue with corrupted state is exactly how a programming error becomes a memory safety vulnerability.
We’ve talked about these before, and we often break memory safety down into five different axes that a programming language needs to address. So these are bound safety, lifetime safety type safety initialization safety and thread safety. The C family of languages doesn’t provide safety for any of these axes, although there are mitigations that can help with some of them. But let’s dig into Swift and how it addresses each of these different areas.
Bound safety is the easiest one. So this is checking to ensure that access is into a block of memory. Don’t go outside that block of memory. As we’ve seen in C, you can adjust the pointer past the end of an allocated block and then go read or modify some other unrelated value, causing a memory safety issue.
Swift code here tries to create the same vulnerability. There’s an array with 12 elements. We try to access the 15th element. Swift is going to do runtime bounds checking to ensure that this immediately traps similar to what we talked about with bound safety. That’s the easy one. It gets more interesting after this.
So lifetime safety is what ensures that when you access memory, that memory is still valid. Now in C, it’s fairly easy to write code that violates lifetime safety with something like a use after free, where the code frees some allocated memory and then tries to access it through this stale pointer, which could now refer to something completely different.
Swift eliminates use after free issues by taking away the free. So if you have some code here, you can allocate an instance of my class. Put it in a local variable. You can use it to call a method on it and note there’s no free anywhere. Instead, the compiler is going to insert the destruction when that instance is no longer used, and the compiler will always do so in the correct manner, right?
The same automatic lifetime management is used in Swift collection classes. These are things like array and dictionary. Now here the array is created on the first line and it’ll be deallocated automatically once it’s no longer used. Swift uses a technique called automatic reference counting. So we chose this over other more traditional garbage collection techniques because it has really good engineering trade offs.
When you have automatic reference counting, you can basically program in Swift without thinking about the memory management. All the patterns seem to work and you’re not considering the lifetime most of the time. Now on the other hand, automatic reference counting. It’s very fast. The overhead is very low, both in performance and in memory usage. And it doesn’t have the kinds of pauses that you might see with a more traditional garbage collection scheme.
However, there are times when you need to ensure that there is zero runtime overhead to maintain lifetime safety. And so Swift also provides non copyable types. These describe ownership over a unique owned resource. There’s a trade off here which is that non copyable types have a more restrictive programming model. You have to start thinking about ownership more. But for that you get the zero overhead. We’re going to come back to non copyable types later on in the talk.
Type safety is what prevents accessing memory with the wrong type. Again, C allows you to easily create type safety problems in a couple of different ways. So say we write an integer into this cell in memory. Now later on we could go through a union member or maybe use an explicit cast and read it back as a file, causing a type confusion.
Swift provides equivalence to these features of C unions and typecasting, but both of them are designed to be safe by construction. Swift enums are discriminating. Discriminated unions, meaning that they encode which of the options is currently active. So here we have the resource enum. It can either store a file or it can store an integer identifier to access a resource. You pattern match using a switch statement, which ensures that you can never access the wrong member because these are the. This is a paired access.
Swift’s typecasting is similar. So the Swift code here defines a class and one subclass. That subclass overrides a method and introduces its own other method. Pretty classical object oriented programming. Here we’re going to try a downcast. Now the as question here will downcast the given object to my subclass. It produces an optional value which will either contain the instance as a my subclass, or it will be empty if the downcast fails.
So if it’s an instance of the subclass, the code will run inside the body of the. If so, we can call the other method that’s safe. If the downcast fails, the optional is empty. The body of the if is not executed. So this eliminates any potential memory safety holes from casting, as well as helping avoid programming errors due to incorrect casts.
Initialization Safety is another fairly direct one. So this is what prevents memory from being read before it is initialized again. C doesn’t require it. So you can construct a local variable and then just use it directly before it’s been initialized. If you try to do the same thing in Swift, the Swift compiler will reject this attempt. So it notes that you have not assigned or initialized the variable and will reject it at compile time. So there can’t be a memory safety issue.
The last and trickiest one is thread safety. So a violation of thread safety means that having a data race anywhere in your program could introduce a memory safety problem. And this can even happen happen even if your language is safe in every other axis. So in this example, here is a shared mutable resource. The function replace resource is going to change the value of that resource, including setting its type to this identifier.
Imagine this is happening in one thread Now in another thread, there’s this use resource function that is switching on that same shared resource. If you had a data race here, it could mean that one thread sees it as a file at the same time that the other thread is overwriting the integer. Overwriting with an integer the actual file instance.
The Swift six concurrency model prevents these data races by ensuring that there aren’t concurrent accesses to shared mutable state. Now, along with that, Swift provides safe ways to deal with concurrency that don’t introduce data races at the high level. One of these is actors, so actors are types that encapsulate their state and protect it from concurrent modification.
The resource variable here now is part of the actor’s state, so Swift ensures that use resource and replace resource that can touch that state will never run concurrently. This is enforced Consistently using Swift’s async await model. So a call to a method on an actor is always asynchronous, because the caller might have to wait until the actor is done executing code on some other thread before it is safe to make that call.
Swift also provides low level primitives for data race safety. The mutex type here describes something that can only be accessed by a single thread at a time. Every access to the data stored in a mutex occurs through this width lock function that provides temporary access to that data. Inside the closure, the mutex ensures that only a single thread will be able to run its closure at the same time.
So with swift memory, safety is woven into the design of the language. There’s a benefit here that’s really hard to convey until you’ve experienced it for yourself. Because when you’re using a memory safe language, it’s not about just not having to worry about. AM I introducing a memory safety vulnerability or look for them? You just stop thinking about it entirely. And so you can focus all of your attention on the correctness of the code and other concerns that aren’t related to memory safety.
Now, if you’re coming from the C family of languages, you’re probably wondering whether this memory safety comes at a cost of performance. So at a high level, Swift is a natively compiled language. It provides efficient implementations for its memory safety model, and for many programs, that’s good enough, but for very low level code that has to match the performance of unsafe C Swift six two introduced a couple of safe abstractions to help.
Let’s take an example in C. This is a decoder for an image that uses some run length encoding. So in the loop it’s reading four bytes at a time from the input buffer. So this is the count and the pixel data, and then it’s writing the result out through an output buffer.
Along the way, you can see it’s manually performing bounds checking. There’s also a lot of assumptions this C code makes that aren’t actually verified. So we’re assuming that the count parameters correctly describe the sizes of the corresponding buffers. We’re assuming the input and output buffers don’t alias in some strange way, and won’t end up getting modified or freed by another thread while this program runs.
Here’s the translation of that same code into Swift. So the input buffer is an array that encapsulates both the data and the count. So you can know. And it also ensures that the data isn’t going to go away or be modified while this function is running, even in a multithreaded program.
Now array accesses are bounds checked, so an error will trap at runtime rather than become a memory safety vulnerability. But I said I’d talk about performance. So let’s look at that. So with correct bounds checking in the loop, a compiler can often optimize away the bounds checks entirely when it’s proven that it’s safe to do so.
Now Swift’s copy on write arrays use reference counting in their implementation. Again, the compiler can often optimize away all the reference counting traffic, which it does in this example. However, this append operation is going to add a new element to an array. If the array doesn’t have enough space in it already, it will have to allocate new memory with more storage and then copy all the existing elements over to it. That’s going to be slower than the C implementation we had before. It was just writing pixels out through a pointer.
There’s a second issue here, which is that this design forces clients to themselves have arrays. We can require them to introduce copies of their own. Here’s an example. We have some data in an array, but it has this simple header consisting of a width and height of the actual output image, followed by the image data we actually want to decode.
Now, the main problem here is that we need to make a copy of all of this run length encoded pixel data, just to call the decode function we’ve been working on. Because our decode wants to read the entire pixel array, that’s an extra heap allocation and a copy of all the image data, which we absolutely do not want.
There’s a related issue here, which is that our decode is going to allocate the result array on the heap all the time. Now this particular image decode operation might be okay with that. But you may have some other caller that wants you to put the pixels in some particular place, maybe into a fixed size frame buffer that’s already been allocated.
And to do that, you would have to copy the results yet again. So this kind of problem can come up a lot in low level, very performance sensitive code bases. You can partially address it using erase slices or generic collections, but those can be kind of hard to work with.
So this is where the new span family of types come in. So spans provide safe low overhead access to contiguous memory. If you’ve used the unsafe buffer pointer types in Swift, you can think of spans as the safe counterparts to those. Now the key idea behind span is that it references contiguous memory that it does not own. You can think of it just like a pointer plus length, because that’s exactly how it’s represented in memory.
Now, span provides full memory safety. It provides lifetime safety in a manner that is checked by the compiler with no runtime overhead whatsoever. It provides bound safety by bounds, checking all accesses. But most importantly, it doesn’t own the storage it references. Instead, it interoperates with the various types that do own storage. You can get a span referencing into a copy on write array or the fixed length inline array. It also interoperates with the unsafe pointer types, which is important when interacting with unsafe languages or code that predates spans.
The span family of types was introduced in Swift 6.2. However, we think span is so important that we made these types back deploy to much older versions of Apple’s operating systems. That way, you can adopt them now without having to raise your deployment target. Okay, it’s time to adopt span for the input to the run length decoder.
This didn’t require a lot. We just changed the parameter type from an array to a span. This works because span provides the same APIs for accessing elements that arrays do. And because this code is just reading through the data, it’s not trying to modify it. It’s not returning copies. Nothing else actually had to change in this function. Now what did change is the API contract callers now need to provide a span instead of an array. So let’s go back to our caller’s code to see how to do that.
Now first of all, every array has a span property that produces a span. So we can make the code compile like this. Just adding the span at the end. That does work. However, it’s not going to improve performance because we’re still creating this new array. Copying all the data before we take the span into it, we can do better.
So instead, what we’re going to do is take a span from the original array and then pass just the slice of it that we care about down to the decode function. Read code receives just the span that’s relevant to it, but there are no extra allocations and no copies while still maintaining memory safety throughout.
This example does make it look like span is just a drop in replacement for array. It’s not. So it’s a reference to storage owned by someone else. And so to make this safe without any runtime overhead, the span type itself has some necessary restrictions. So span is what we call a non escapable type. It’s written with this tilde escapable syntax shown above. You can use this same syntax to define your own non escapable types that behave like span does.
Now with a non escapable type you can always pass it down as an argument to a function like we did earlier. However, you can’t return any span out of a function because you can only return a value of non escapable type if its lifetime is tied to one of the parameters.
Let’s dig into the body of the first run function to see what this means. So the code here is finding a run of elements that match the first element. And the loop inside is going to break out when it finds the first mismatch. The critical part here isn’t that logic, but the return at the end.
Now here, what do we return? We return the span extracted directly from the data parameter that we got. It’s just the relevant portion of it. And that’s okay. It means that the caller that provided us with the span will keep that resulting span alive long enough. However, imagine that the code instead made a copy of the data into a new array. This array is stored in a local variable, and then it tries to return a span from that copy out. The compiler is going to produce an error here.
The reasoning is that this newly created array is stored in a local variable. That’s what’s keeping it alive. We can’t return a span into a local variable up to our caller, because the local variable is going to go away as soon as we exit. So if you think in terms of C, the restrictions I’m talking about might sound familiar.
Imagine you take the address of a local variable. You have to be very careful with that pointer to make sure it doesn’t get saved somewhere, and then get used after your function has returned and the local variable has gone away. Swift makes those restrictions part of the language, so you can’t make a mistake that compromises memory safety.
Span by itself provides read only access to memory. There’s another type mutable span that provides mutating access so you can both write as well as read. You can use the mutable span property from any of the contiguous collections I talked about earlier to get immutable span into its storage, and then modify elements there using Subscripting, just as you would expect.
Now, a crucial part of the safety model here is that mutable span requires exclusive access to the underlying memory. That means if you have immutable span referring to some block of memory. Nobody else has access to that same memory. Not for writing and also not for reading. And so in our code, if we were to create a second span that accesses inside that same array and then try to go do a mutation, the compiler is going to produce an error here to prevent any accesses to the storage while there’s still an active mutation.
Now this exclusivity model is fundamental to Swift. It’s actually been in place since the beginning. It’s what ensures that memory safety when using things like mutating methods and inout parameters. Most Swift programmers don’t even know it exists, because it’s very rare to hit these situations where it’ll actually trigger a memory safety violation, but it’s there as a backstop to provide memory safety. Okay, so now it’s time to adopt immutable span in the run length decoding function instead of returning a heap allocated array. This one’s going to require a little bit more work than span did. But again we’re going to start with the function signature.
The key here is that instead of creating new storage that’s returned to the caller. The caller is going to tell us where to put the results via this output parameter. It’s passed in out because when you’re modifying a mutable span, that’s where the results go. And note that instead of appending to an output array, the pixels are now written directly into their final place through this out parameter.
This means there’s no longer any memory allocation in this function. It’s all up to the caller to set up the read and write buffers, just like we did in C. Speaking of the caller. So it actually previously relied on the fact that early decode returned an array. So let’s go back and look at that.
Now what this function is going to need to do is allocate its own array of pixels. And then it’s going to pass down a mutable span into that array to read code to fill in the resulting pixels. This caller, of course, is choosing to do the heap allocation, but a different caller could make a completely different choice.
Here’s an example. This structure here is representing a 320 by 240 frame buffer of pixels. It’s represented as an in-line array to avoid heap memory allocation. The image decode operation takes a mutable reference to the frame buffer it was given again using Inout. It then gets immutable span into those pixels and passes that down to our decode. See what’s happening here? The decode is going directly into the frame. No. No extra copies. No memory allocation.
Adopting span is a significant win for both performance and memory safety. We encourage you to adopt it in your Swift Code bases. If you’re looking for the places where you might need to adopt span, There are two starting points from a performance perspective. Look for performance sensitive code that’s using the array or the data types.
If you notice extra copies or heap allocations, use span instead, as we did with the decode operation from a memory safety perspective. Start by replacing uses of unsafe buffer pointer types in your code. As their name implies, the unsafe pointer types do not maintain memory safety and should be used rarely. Span is a safe alternative for most uses of unsafe pointers, although it may take you some refactoring to get there. Now I’ll hand things over to my colleague Felix to talk more about how to deal with unsafe constructs in Swift without compromising on memory safety.
Thanks, Doug. Hi, my name is Felix and I’m from a security engineering and architecture team. The next topic on the agenda is safely using unsafe code. Now, memory safe languages are the future of programming. As Doug explained, by using low overhead primitives like span, safe code is very fast and safe code cannot introduce memory safety bugs. At the same time, unsafe code is everywhere today. Literally.
And even as safe languages are picking up in areas where they were hard to use before. There are still billions of lines of unsafe code to account for. This has been known for decades. Unfortunately, it’s common engineering wisdom that grounds up. Rewrites are risky. A new implementation can introduce or re-introduce bugs, and when the existing implementation continues to evolve at the same time as the unsafe implementation, it competes with it.
This is why it’s important to make plans to migrate incrementally from unsafe code by taking smaller rewrite bites. The risk becomes much easier to manage and the chances of success improve massively. And the reason this matters today is that Swift has unique capabilities that were designed so that incremental migrations from C, C++, and Objective-C are possible.
The first of these tools is strict memory safety. There is no safer way to work with unsafe code than by enabling strict memory safety. This is very important. Strict memory safety reveals all uses of unsafe code. This is very useful because while. While Swift often puts unsafe in the name of unsafe things, some operations are implicitly unsafe.
For instance, in this code, the copy over array function declares two array variables x and y, and it uses memcpy to copy one to the other. The code doesn’t say that Memcpy does anything unsafe, but Memcpy is a C function, so it accepts unsafe pointers. X and y are converted to unsafe pointers implicitly. This could cause a memory safety bug with no clear indication. Once you enable strict memory safety, the compiler will warn. For this and all other uses of unsafe code with expression uses unsafe constructs but is not marked with unsafe.
This is resolved by adding the unsafe keyword in front of the call. I’ll take a minute to expand on the unsafe keyword. Outside of addressing the compiler warning, it has two main functions. First, it’s a warning for security reviewers when auditing a code base. The unsafe keyword is an indication that something potentially dangerous is happening.
And second, and even more importantly, it’s a reminder that you must verify something that the compiler is unable to verify. Bringing back the Memcpy example. The code is correct because both arrays contain four bytes. However, the compiler does not know that this is a precondition for the call. The unsafe keyword is a reminder to verify that Memcpy is used correctly. To enable it in Xcode projects. Look for the strict memory safety setting under Swift Language Options. In a Swift package. Strict memory safety is enabled by adding the strict memory safety setting to the package description.
Now, even though strict memory safety is enabled and will ensure that unsafe code is visible, it’s still best to avoid using unsafe functionality entirely. There are only two cases where unsafe code is truly necessary. First, to interoperate with unsafe libraries and second to implement safe primitives. In many cases, there are actually two facets of the same little unsafe gem.
Writing new code in safe languages has many benefits, but an unsafe implementation unsafe implementation may still be the best tool for some tasks. This is because unsafe libraries are common, and security aside, may still be the most mature. When it’s possible to rewrite an unsafe library in a safe language. That’s always preferable, but it’s not always possible on an arbitrary time frame. Safe rewrites take time and expertise until the time and expertise are available. Swift can help ensure that the library is used safely.
Doug introduced the decode function earlier. Say that it’s implemented in an external C library that can’t be rewritten easily. When Swift sees the header, it will expose the function as this swift, unsafe interface. It takes the same parameters a source pointer, a source size destination pointer, and a destination count. And it’s not ideal because it still uses unsafe values, but it can still be called from Swift. However, with strict memory safety enabled, the compiler emits the same unsafe constructs. Warning.
It’s better to write a safe wrapper of unsafe implementation like Doug showed. This wrapper takes a span as input, mutable span as output. That wrapper will have a lot of mentions of unsafe in it. This is because each operation that takes a pointer out of a span or uses those pointers needs to be marked unsafe. However, it’s easy to audit for security.
The code takes an unsafe pointer out of each span, and it passes it along with the corresponding counts to the C implementation. It’s not necessary to audit users of the Swift interface, because it will pass spans, and spans are safe, and there is no mistake here. But if there was one, it would be in this implementation. There could also be a memory safety bug in the C implementation. However, the calling Swift module is cleared of wrongdoing when red code gets a safe implementation. This risk is eliminated entirely.
Now, on the side of implementing safe primitives, something else that might happen when wrapping unsafe code is that an external library could be vending. Some kind of resource that must be created and destroyed manually. Here’s the really decode function again. I modified it so that instead of a single stateless decode function, now you need to create a decoder object with RL in it, and then you need to destroy it with RL destroy. And the RL decode function is mostly the same as before, but now it takes a decoder as a first argument.
This pattern needs an explicit Dianette, since Copyable structs can’t have one. This was always implemented with a class. A lot of code that encapsulates unsafe resources looks like this today. The initializer calls the RL init function, and then the initializer calls the RL destroy function. And the decode function is going to be vastly the same as it was from before.
This works, but it’s not as efficient as it could be. First class instances are allocated dynamically. This is usually not a problem for long lived objects, but creating and destroying instances repeatedly leads to avoidable calls to malloc and free. And it’s even more expensive to use a dynamic allocation to wrap another dynamic allocation.
Second class instances are reference counted. This allows complex memory management situations to remain safe, but objects can end up paying for reference count traffic even if they have very simple lifetimes. And last, all fields of a class instance have fine grained exclusivity checks at runtime, and this allows more flexible aliasing operations than you can do on structs. But again, types with simple operations may not benefit at all and still pay the cost.
Class instances are very versatile, but they can be more bulky than necessary. These are small overhead costs, but when you compare it to an unsafe implementation that does no checking at all, they add up quickly. Since Swift six, you can use non copyable structs for these use cases. Trucks don’t need that dynamic allocation. So that’s one source of overhead down. And they have course exclusivity checks that are mostly verifiable at compile time. So the program has fewer of them to begin with and they are easier to optimize away.
However, sharing a non copyable struct is much more constrained than a class or a Copyable struct, thanks to the flexibility of reference types in the decode one function. There is no problem having multiple references to the same decoder and calling the decode method through either of them. However, it would be an error to do the same thing with a non copyable struct.
The compiler would immediately diagnose at the object, a is moved into B and then used again. So in short, for simple object management, there are lots of performance benefits to non copyable structs over classes. You may not always be able to use them because they are less flexible. It’s more specific, but in exchange, you get more predictable performance.
The last thing I want to talk about is calling Swift from C. All major platforms currently have an unsafe C or C++ core, and all safe languages must call into that core somehow. Mixing with C is a normal and expected capability of safe languages. Regardless, it is frequently difficult.
One reason it can be difficult is that different languages have different expectations, and when one language calls into another one, the expectations of both languages have to hold. For instance, in a language that uses garbage collection, passing an object pointer to C may require special collaboration with the garbage collector to prevent it from moving while the object is still.
References from C. And another reason is that most languages don’t understand each other very well. For instance, in many languages that can call in to C, interop needs glucose. That describes the C header file because the targets language compiler can’t read C headers. Writing that code is tedious.
But thankfully, Swift was designed for interop with unsafe languages. Swift eliminates most of that complexity for developers by embedding an entire C compiler. It can interpret and make arbitrary declarations available from header files, including inline functions, by parsing them from the bridging header. Projects can include arbitrary headers from their bridging headers, and Swift can make declarations available to C compilers by generating a header of its own.
I am bringing back Doug’s early decode example to the screen. Doug showed how an implementation using span is the most flexible option. That implementation was used as a unique backend for two other functions that returned pixels in completely different ways. First one that returns an array, and second one that returns its output by reference into a mode X frame structure. Starting with Swift 6.3, the span implementation can also be the backend for a C function, such as the one that Doug started with.
This is an implementation that Doug introduced yet. One last good look at it, because I am going to delete it to replace it with a swift implementation. To do this, first there needs to be a Swift function that has a prototype that’s compatible with the target C function.
Here there is ready code that takes an input pointer, an account, an output pointer, in account, and then it creates a span over the input pointer span over the output pointer. And it calls the common read code Swift backend. Next, the function needs to be exposed to see there are two ways to do it. The first one is to add the at c attribute to the function.
When using at C, Swift translates its types to reasonable corresponding C types. For instance, the Swift Int32 type translates to int32t l c basic integer type depth translate to their expected counterpart C to char c, short to short, c, end to int, etc. mind that int translates to pointer div t, and. This is a reason that our code accepts and returns pointer div t instead of size t when implementing.
When the function to implement is already visible in the bridging header, there is a second option using the at implementation at C combination like when using Objective-C methods with at implementation instead of emitting a declaration in your generated header. This asks the compiler to find an existing declaration for the decode function from your bridging header.
When using this method, the compiler verifies that the Swift prototype can match the C prototype, and it generates an error if they’re not compatible. When using just at C, Swift has to pick argument types, but when using at implementation at C, it’s able to adapt to some other conversions. For instance, if the C prototype uses size T, Swift will also accept an implementation that receives int instead of Uint.
Now, this is a great opportunity to look back at what just happened. On my right there is a C function called cats and dogs that identifies pets in pictures. It takes a pointer to an image structure and a pointer to a buffer of pet Bet records. It managed pixel memory for decompressing the image, and it calls the decode function to decompress the image. And lastly, it identifies it calls identify cats and dogs to find where the cats and dogs are in the picture.
Before this would call the C implementation of decode on the right that dog showed earlier, but by using at implementation at C without having to change anything in a scholar, the cats and dogs function now uses the safe decode implementation, and this worked seamlessly from Swift. Because a Swift compiler is able to match the decode implementation with the same C function prototype that clang sees for decode.
And that’s all that’s needed at C and at implementation are great tools to start migrating a C code base to Swift and write new features in safe languages when they need to be used by C callers. Now that you’ve learned about span, move only types and interrupt between C and Swift. Here’s what you need to do next. First, use strict memory safety in your code.
This will enable you to account for unsafe operations in your code bases. Next, start using span to access contiguous memory safely. Start with replacing uses of unsafe buffer pointer. Then create safe interfaces by encapsulating your unsafe resources when possible with move only types classes otherwise. And finally, start gradually migrating your unsafe code to Swift using its interop features. Thank you for your attention. Memory Unsafety is today’s biggest source of security bugs, and I’m looking forward to building a safer world together.