Transpilation is the process of converting one computer language into another language with a similar level of abstraction. The Skip transpiler converts your Swift source code into the equivalent Kotlin for running on Android. This document describes the trade-offs of Skip’s use of transpilation compared to the more traditional compilation.

Note: Skip does not alter or affect your Swift code when it is compiled and run on iOS. This document therefore focuses solely on Skip’s transpilation for Android.

Benefits

The primary benefit of Skip’s use of transpilation is near-perfect integration with native API. Because your Swift code is converted to Kotlin, it can directly call other Kotlin API, just as if it were calling Swift. And because the Kotlin language features seamless integration with Java, your Swift code can call Java API as well.

Skip also unifies the languages’ type systems. Swift primitives are transpiled into the corresponding Kotlin primitives. Swift strings become Kotlin strings. Your Swift classes, structs, enums, and protocols translate into Kotlin classes, enums, and interfaces. Even closures and asynchronous calls are compatible. This means that when interacting with native API - whether platform libraries, third-party libraries, or parts of your app you’ve decided to implement natively - your code can conform to required interfaces, provide callbacks, and pass data back and forth without complex bridging.

The ability to so easily integrate with native code even extends to the UI layer. Skip provides simple mechanisms to embed Compose UI code into your SwiftUI and vice versa. Internally, this is also what allows Skip to implement SwiftUI on top of Compose, delivering a fully native UI on both platforms. This type of interoperability would become much more difficult if your Swift were compiled straight to Android machine code.

We believe that this ability to trivially work with platform-specific API is a huge benefit to any multi-platform product. It allows you to decide exactly how much code to share and where and how to customize your code for each platform, without friction. It is especially beneficial, however, to a young tool like Skip. You can be sure that if you run into a limitation of Skip’s dual-platform API coverage, you can easily specialize your code to call the native iOS or Android API instead.

The second major benefit of transpilation is also of particular value to a new development tool: transparency. Transpilation allows you to see and understand all of Skip’s output. You don’t have to trust that Skip isn’t doing anything nefarious or unreasonable in the Android code it generates, because you can examine the transpiled Kotlin for yourself. And if there is a bug in Skip’s transpilation - or if some bit of translation requires hand optimization - it won’t be buried in machine code. Skip’s Kotlin is fully human-readable, debuggable, and even overridable: Skip includes the ability to insert or substitute literal Kotlin inline with your Swift.

Finally, transpilation makes Skip ejectable. If Skip were to disappear, you would still have the full source code to both the iOS and Android versions of your app. You could continue to evolve the app as separate iOS and Android codebases (which is how many dual-platform apps are developed). Skip does not have any required runtime components other than the libraries it uses to provide the Foundation, SwiftUI, etc APIs on Android, and these libraries are all free and open-source. We understand the difficulty of placing your business in the hands of a development tool. With Skip, the future of your dual-platform app is secure.

Downsides

The biggest downside of Skip’s transpilation is that although Skip supports the vast majority of Swift constructs, it does not support the complete language. While it is technically possible for any Turing-complete language to transpile to any other, in practice Skip is limited by two factors: the transpiler’s sophistication and what is naturally expressible in Kotlin.

Swift is a complex language. The Skip transpiler is not yet able to perform Swift type inference as well as the full Swift compiler. This means that you may occasionally have to declare your types more explicitly, fully qualify static values and enum cases, or provide unique names for heavily-overloaded functions. The need for these sorts of workarounds will decrease over time as Skip’s transpiler continues to improve.

Swift constructs with missing or incompatible Kotlin analogs pose a more difficult problem. Skip’s transpiler is already able to intelligently bridge many of the differences between Swift and Kotlin, and we have ideas for future enhancements to expand Swift language support. If Skip wants to maintain a unified type system and API call compatibility between transpiled and “normal” Kotlin code, however, there are limits to the language differences that Skip can bridge. Fundamental incompatibilities between the languages - such as the differing ways that Swift and Kotlin handle generics, or Kotlin’s lack of static protocol requirements - may take a long time to solve, and the solution may negate some of transpilation’s benefits.

Conclusion

Software engineering is all about trade-offs. This document explored the benefits and downsides of Skip’s use of transpilation for running Swift code on Android. We believe that the seamless interoperability with native Android code that transpilation provides currently makes it the right solution for Skip. Should a robust, modern Swift compiler for Android emerge, however, Skip would explore how to continue to integrate with Kotlin and Java while taking advantage of the compiler’s more complete Swift language support.