<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://davidederosa.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://davidederosa.com/" rel="alternate" type="text/html" /><updated>2026-04-21T08:56:23+02:00</updated><id>https://davidederosa.com/feed.xml</id><title type="html">Davide De Rosa</title><subtitle>I make software. I look around me.</subtitle><author><name>Davide De Rosa</name></author><entry><title type="html">Cross-platform Swift: Integration (pt. 2)</title><link href="https://davidederosa.com/cross-platform-swift/integration-part-two/" rel="alternate" type="text/html" title="Cross-platform Swift: Integration (pt. 2)" /><published>2026-03-15T00:00:00+01:00</published><updated>2026-03-15T00:00:00+01:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-integration-part-two</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/integration-part-two/"><![CDATA[<p>In the <a href="/cross-platform-swift/integration-part-one/">previous post</a>, I threw in a few ideas about how to integrate Swift logic with non-Swift code. We learned how to call Swift code from other languages with <a href="/cross-platform-swift/integration-part-one/#introducing-_cdecl"><code class="language-plaintext highlighter-rouge">@_cdecl</code></a>, but now we need to fill a few remaining gaps to build a modern non-Swift application on top of our Swift library.</p>

<p>You can find the <a href="https://github.com/keeshux/cross-platform-swift/tree/master/Sources/SimpleABIConsumer_C">source code</a> of these examples on GitHub.</p>

<h2 id="asynchronous-calls">Asynchronous calls</h2>

<p>There’s no such thing as concurrency keywords in C. The only tool we have at disposal to return an async result is with a callback. To some extent, this reminds of completion handlers before Swift Concurrency entered the scene. However, Swift completion handlers are still closures, as in they can capture variables from the enclosing context. This is not the case for C callbacks, which are stateless pointers to function and therefore closer to <code class="language-plaintext highlighter-rouge">static</code> functions. The common pattern to mimic Swift completion handlers in C is by providing a <strong>callback context</strong>. Let’s go step by step.</p>

<h3 id="c-types-in-arguments">C types in arguments</h3>

<p>Say we want to get the result of this <code class="language-plaintext highlighter-rouge">async</code> function:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftSlowResult</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>
    <span class="o">...</span> <span class="c1">// Code may throw here</span>
    <span class="k">return</span> <span class="mi">100</span> <span class="c1">// Whatever works, we only care about the function signature</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We cannot attach a <code class="language-plaintext highlighter-rouge">@_cdecl</code> attribute as long as we use features that don’t transpose to C, and <code class="language-plaintext highlighter-rouge">async</code> is one of them. Let’s take an intermediate step and wrap the function like the preconcurrency counterpart of many standard Swift APIs:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftSlowResultWrapper</span><span class="p">(</span>
    <span class="nv">completion</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="p">(</span><span class="kt">Int</span><span class="p">?,</span> <span class="kt">Error</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span>
        <span class="k">do</span> <span class="p">{</span>
            <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">swiftSlowResult</span><span class="p">()</span>
            <span class="nf">completion</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
            <span class="nf">completion</span><span class="p">(</span><span class="kc">nil</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Cool, we got rid of <code class="language-plaintext highlighter-rouge">async</code> with the callback pattern. As we saw before, optionals and <code class="language-plaintext highlighter-rouge">Error</code> are other points of friction, so we introduce a user-defined error code enum, that is a plain <code class="language-plaintext highlighter-rouge">int</code> in C. All things considered, this is the first half-working version of our signature:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">SlowError</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="kt">Error</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">foo</span> <span class="o">=</span> <span class="mi">10</span>
    <span class="k">case</span> <span class="n">bar</span> <span class="o">=</span> <span class="mi">20</span>
    <span class="k">case</span> <span class="n">unknown</span> <span class="o">=</span> <span class="mi">1000</span>
<span class="p">}</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"swift_slow_result"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftSlowResultWrapper</span><span class="p">(</span>
    <span class="nv">completion</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="p">(</span><span class="kt">Int</span><span class="p">,</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="p">{</span>
    <span class="kt">Task</span> <span class="p">{</span>
        <span class="k">do</span> <span class="p">{</span>
            <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">swiftSlowResult</span><span class="p">()</span>
            <span class="c1">// Error code 0 means success</span>
            <span class="nf">completion</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="k">let</span> <span class="nv">error</span> <span class="k">as</span> <span class="kt">SlowError</span> <span class="p">{</span>
            <span class="c1">// The first argument must be ignored on failure</span>
            <span class="nf">completion</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">error</span><span class="o">.</span><span class="n">rawValue</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
            <span class="nf">completion</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="kt">SlowError</span><span class="o">.</span><span class="n">unknown</span><span class="o">.</span><span class="n">rawValue</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And a trivial example of its usage:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="k">enum</span> <span class="p">{</span>
    <span class="n">slow_error_none</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">slow_error_foo</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span>
    <span class="n">slow_error_bar</span> <span class="o">=</span> <span class="mi">20</span><span class="p">,</span>
    <span class="n">slow_error_unknown</span> <span class="o">=</span> <span class="mi">1000</span>
<span class="p">}</span> <span class="n">slow_error</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">my_result_callback</span><span class="p">(</span><span class="kt">int</span> <span class="n">result</span><span class="p">,</span> <span class="n">slow_error</span> <span class="n">error_code</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">error_code</span> <span class="o">!=</span> <span class="n">slow_error_none</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Error: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">error_code</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Result: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">invoke_swift_async_function</span><span class="p">()</span> <span class="p">{</span>
    <span class="cm">/* ... */</span>
    <span class="n">swift_slow_result</span><span class="p">(</span><span class="n">my_result_callback</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="providing-context">Providing context</h3>

<p>Another problem to address is supporting captures behavior in the <code class="language-plaintext highlighter-rouge">completion</code> callback, because we probably want to do something useful upon completion. The <em>de facto</em> way to do it in C is, as mentioned before, by propagating a context:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftSlowResultWrapper</span><span class="p">(</span>
    <span class="nv">ctx</span><span class="p">:</span> <span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span>
    <span class="nv">completion</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="p">(</span><span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span> <span class="kt">Int</span><span class="p">,</span> <span class="kt">Int</span><span class="p">)</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="p">{</span>
    <span class="nf">nonisolated</span><span class="p">(</span><span class="n">unsafe</span><span class="p">)</span> <span class="k">let</span> <span class="nv">unsafeCtx</span> <span class="o">=</span> <span class="n">ctx</span>
    <span class="kt">Task</span> <span class="p">{</span>
        <span class="o">...</span>
        <span class="nf">completion</span><span class="p">(</span><span class="n">unsafeCtx</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">errorCode</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Unsafe and generic by nature, a context can be literally anything. Unsurprisingly, Swift <code class="language-plaintext highlighter-rouge">UnsafeMutableRawPointer</code> maps to <code class="language-plaintext highlighter-rouge">void *</code> in C.</p>

<p>This is the second version of our C program interacting with Swift async code:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Initialized elsewhere. */</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">initial_value</span><span class="p">;</span>
    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">perform</span><span class="p">)(</span><span class="kt">int</span><span class="p">);</span>
    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">report_failure</span><span class="p">)(</span><span class="n">slow_error</span><span class="p">);</span>
<span class="p">}</span> <span class="n">complex_object</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">my_result_callback</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span> <span class="kt">int</span> <span class="n">result</span><span class="p">,</span> <span class="n">slow_error</span> <span class="n">error_code</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">complex_object</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="p">(</span><span class="n">complex_object</span> <span class="o">*</span><span class="p">)</span><span class="n">ctx</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">error_code</span> <span class="o">!=</span> <span class="n">slow_error_none</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Error: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">error_code</span><span class="p">);</span>
        <span class="n">obj</span><span class="o">-&gt;</span><span class="n">report_failure</span><span class="p">(</span><span class="n">error_code</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">compound_result</span> <span class="o">=</span> <span class="n">obj</span><span class="o">-&gt;</span><span class="n">initial_value</span> <span class="o">+</span> <span class="n">result</span><span class="p">;</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Compound result: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">compound_result</span><span class="p">);</span>
    <span class="cm">/* Move on with our lives. */</span>
    <span class="n">obj</span><span class="o">-&gt;</span><span class="n">perform</span><span class="p">(</span><span class="n">compound_result</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/* We assume `obj` to be the context to act upon, and we expect it
 * to survive the synchronous function call. A pointer to a local
 * variable cannot be the context of an async function, because
 * the callback would eventually refer to a dangling pointer.
 */</span>
<span class="kt">void</span> <span class="nf">invoke_swift_async_function</span><span class="p">(</span><span class="n">complex_object</span> <span class="o">*</span><span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">swift_slow_result</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">my_result_callback</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="calling-convention">Calling convention</h3>

<p>Nevertheless, the code lacks a final touch, also a very obscure one. As is, the signature still accepts a Swift closure (or ObjC block) for the <code class="language-plaintext highlighter-rouge">completion</code> argument, and this will inevitably lead to arcane compile issues or runtime crashes because of the wrong <strong>calling convention</strong>.</p>

<p><img src="/s/f/cross-platform-swift/integration-02-callback-crash.png" alt="Obscure crash in C due to wrong calling convention" /></p>

<p>We must enforce the <code class="language-plaintext highlighter-rouge">@convention(c)</code> attribute to restrict the input to C-style pointers to function, i.e., without captures:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftSlowResultWrapper</span><span class="p">(</span>
    <span class="nv">ctx</span><span class="p">:</span> <span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span>
    <span class="nv">completion</span><span class="p">:</span> <span class="kd">@Sendable</span> <span class="kd">@convention</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">(</span><span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span> <span class="kt">Int</span><span class="p">,</span> <span class="kt">Int</span><span class="p">)</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
</code></pre></div></div>

<p>I might be wrong, but as far as I can tell, the Swift compiler is unable to enforce the C calling convention in <code class="language-plaintext highlighter-rouge">@_cdecl</code>-enabled functions, because I believe it should.</p>

<h2 id="event-handling">Event handling</h2>

<p>Another crux of a modern application is delivering and handling asynchronous events at scale. In the recent years, Combine was the most used tool for this purpose, and the SwiftUI <code class="language-plaintext highlighter-rouge">onReceive()</code> function certainly favored Combine publishers for emitting events. But… remember? <a href="/cross-platform-swift/combine/">Combine is not available</a> outside Apple.</p>

<p>Regardless of how we emit events, though, that is a concern of the Swift library. When we build the non-Swift side of the app, we only need a way to <em>observe</em> the events that the library emits. No magic involved, C is a simple language: we can register to events with a callback. The inherent difference with completion handlers, for example, is that an event callback will be a pointer to function that follows the application lifecycle.</p>

<h3 id="emit-the-events-from-swift">Emit the events from Swift</h3>

<p>Let’s start with a dumb events emitter. Here we emit events represented by integer codes from 1 to 3 at a 500 milliseconds interval. We use the same callback mechanism with an optional context. The consumer will start the events emitter and subscribe to it contextually.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">EventCode</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="kt">CaseIterable</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">one</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="k">case</span> <span class="n">two</span>
    <span class="k">case</span> <span class="n">three</span>
<span class="p">}</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"swift_start_events_emitter"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">swiftStartEventsEmitter</span><span class="p">(</span>
    <span class="nv">ctx</span><span class="p">:</span> <span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span>
    <span class="nv">callback</span><span class="p">:</span> <span class="p">(</span><span class="kd">@Sendable</span> <span class="kd">@convention</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">(</span><span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)?</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nf">nonisolated</span><span class="p">(</span><span class="n">unsafe</span><span class="p">)</span> <span class="k">let</span> <span class="nv">unsafeCtx</span> <span class="o">=</span> <span class="n">ctx</span>
    <span class="kt">Task</span> <span class="p">{</span> <span class="kd">@Sendable</span> <span class="k">in</span>
        <span class="k">while</span> <span class="kc">true</span> <span class="p">{</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">randomEvent</span> <span class="o">=</span> <span class="kt">EventCode</span><span class="o">.</span><span class="n">allCases</span><span class="o">.</span><span class="nf">randomElement</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span> <span class="p">}</span>
            <span class="nf">callback</span><span class="p">?(</span><span class="n">unsafeCtx</span><span class="p">,</span> <span class="n">randomEvent</span><span class="o">.</span><span class="n">rawValue</span><span class="p">)</span>
            <span class="k">try</span> <span class="k">await</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">sleep</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="nf">milliseconds</span><span class="p">(</span><span class="mi">500</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="subscribe-to-the-events-from-c">Subscribe to the events from C</h3>

<p>We reuse the same object from the previous example as the context, after adding a new method:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">on_event</span><span class="p">)(</span><span class="kt">int</span><span class="p">);</span>
<span class="p">}</span> <span class="n">complex_object</span><span class="p">;</span>
</code></pre></div></div>

<p>So that the callback can be simply:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">my_event_callback</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span> <span class="kt">int</span> <span class="n">event_code</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">complex_object</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="p">(</span><span class="n">complex_object</span> <span class="o">*</span><span class="p">)</span><span class="n">ctx</span><span class="p">;</span>
    <span class="n">obj</span><span class="o">-&gt;</span><span class="n">on_event</span><span class="p">(</span><span class="n">event_code</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When the application starts, we also start the emitter and register to it with our callback:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">swift_start_events_emitter</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">my_event_callback</span><span class="p">);</span>
</code></pre></div></div>

<p>And that’s basically it. The <code class="language-plaintext highlighter-rouge">on_event()</code> method can then forward the event to other areas of the application as it pleases.</p>

<h2 id="data-exchange">Data exchange</h2>

<p>Okay, this can be <em>really</em> annoying, especially if your application is not a greenfield: exchanging strongly typed data across the Swift boundary. There’s a fundamental issue with any form of FFI, which is exchanging data between different programming languages. Keep in mind that returning opaque C pointers to Swift objects with <code class="language-plaintext highlighter-rouge">Unmanaged</code> is not comparable, because the C code is not meant to understand the contents of the memory. Such pointers are treated like integer identifiers. The trouble begins when we expect C code to understand the <em>layout</em> of Swift structures.</p>

<h3 id="the-single-source-of-truth">The single source of truth</h3>

<p>The best way to deal with data is to pick a common format like JSON or <a href="https://protobuf.dev/">Google Protocol Buffers (protobuf)</a>, that is, formats that are easy to encode and decode in any programming language. The approach is as follows:</p>

<ul>
  <li>Define your application entities in an <strong>Intermediate Representation</strong> (IR).</li>
  <li>Autogenerate the Swift and non-Swift models from the IR, the single source of truth.</li>
  <li>Pass text (JSON) or binary data (protobuf) whenever a <code class="language-plaintext highlighter-rouge">@_cdecl</code> function requires a complex entity.</li>
  <li>Encode and decode entities to the native representation as needed.</li>
</ul>

<p>For what it’s worth, <a href="https://quicktype.io/">Quicktype</a> and JSON Schemas can be a simpler IR alternative to <code class="language-plaintext highlighter-rouge">protobuf</code>.</p>

<h3 id="example-flow">Example flow</h3>

<p>Below we define a Swift function taking an entity input encoded as JSON:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* The entity is autogenerated. */</span>
<span class="kd">public</span> <span class="kd">struct</span> <span class="kt">Entity</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">num</span><span class="p">:</span> <span class="kt">Int</span>
    <span class="k">let</span> <span class="nv">ch</span><span class="p">:</span> <span class="kt">Character</span>
<span class="p">}</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"swift_function"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">swift_function</span><span class="p">(</span><span class="nv">obj</span><span class="p">:</span> <span class="kt">UnsafePointer</span><span class="o">&lt;</span><span class="kt">CChar</span><span class="o">&gt;</span><span class="p">?)</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">obj</span><span class="p">,</span> <span class="k">let</span> <span class="nv">json</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">entity</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">JSONDecoder</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">Entity</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="n">json</span><span class="p">)</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"Entity: </span><span class="se">\(</span><span class="n">entity</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"Failure: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is how we invoke the Swift function in C:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* The entity and the JSON encoder are autogenerated. */</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">num</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">ch</span><span class="p">;</span>
<span class="p">}</span> <span class="n">entity</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">char</span> <span class="o">*</span><span class="nf">encode_json</span><span class="p">(</span><span class="k">const</span> <span class="n">entity</span> <span class="o">*</span><span class="p">);</span>

<span class="n">func</span> <span class="nf">c_function</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">entity</span> <span class="n">obj</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">100</span><span class="p">,</span> <span class="sc">'a'</span> <span class="p">};</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">json</span> <span class="o">=</span> <span class="n">encode_json</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
    <span class="n">swift_function</span><span class="p">(</span><span class="n">json</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="n">json</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="limitations">Limitations</h3>

<p>Unfortunately, the IR approach is unfriendly to apps that were written with a Swift-only mindset, and both Passepartout and Partout have a long-standing set of <code class="language-plaintext highlighter-rouge">Codable</code> domain entities. The switch to a JSON or <code class="language-plaintext highlighter-rouge">protobuf</code> codegen can be very challenging if you like me have relied on:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">RawRepresentable</code></li>
  <li>Enums with associated values</li>
  <li>Custom <code class="language-plaintext highlighter-rouge">Codable</code> implementations</li>
  <li>Other strongly typed semantics</li>
</ul>

<p>Because there are often no 1:1 counterparts in other languages. Especially if the entities are involved in a persistence layer, you must be extremely careful with any refactoring of the data model to avoid data loss or crashes.</p>

<p><del>In my case, I tried to rewrite my domain with JSON Schema or <a href="https://github.com/apple/swift-protobuf">swift-protobuf</a>, but eventually chose to write my own codegen with <a href="https://github.com/swiftlang/swift-syntax">SwiftSyntax</a> to keep Swift as the source of truth. The codegen parses an IR from Swift and proceeds to transpile it to Kotlin for Android and C++ for Linux and Windows (later). It was tiring but safer because I didn’t need to change much of the application code, whereas a strict JSON domain would have been a potentially disruptive change. Better be safe than sorry.</del></p>

<p>UPDATE 03/22/2026: Rather than generating the code myself, my strategy for Partout today is hybrid and consists of the steps below:</p>

<ul>
  <li>Use Swift as the source of truth, and stay clear of any fancy way to customize serialization</li>
  <li>Make <a href="https://github.com/partout-io/codegen">partout-codegen</a> to parse a decent subset of Swift AST with <a href="https://github.com/swiftlang/swift-syntax">SwiftSyntax</a> into a restricted IR model</li>
  <li>Let the codegen convert the IR to an <a href="https://github.com/partout-io/partout/blob/4e013d27f196715aa4ba47ebcb83f630acc7e05d/scripts/openapi.yaml">OpenAPI YAML</a> schema</li>
  <li>Use the YAML with <a href="https://openapi-generator.tech/">openapi-generator</a> to generate the data model for any other programming language</li>
</ul>

<p>Unfortunately, I had to provide custom <code class="language-plaintext highlighter-rouge">Codable</code> implementations for any enum with associated values that was involved in serialized data. Kotlin sealed classes map way better to OpenAPI. All in all, the approach works very well to maintain data integrity across polyglot consumers of the library.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>It’s been a long journey, but this article should wrap up everything you need to know to bring your Apple-only Swift/SwiftUI app to any other platform that Swift supports. It’s now time to make sense of all this yapping and build a real application with what we learned from Passepartout/Partout.</p>

<p>Going forward, we’ll build a simple –but thorough– non-Swift application on top of a Swift library. The sample application will feature a native UI (both SwiftUI and non-Swift) interacting with Swift business logic through a C ABI layer. In other words, a scalable architecture to port a SwiftUI app to Linux, Android, and Windows.</p>

<p>Last but not least, even friendly to AI agentic coding. See you in the next article.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><category term="make" /><category term="cmake" /><summary type="html"><![CDATA[In the previous post, I threw in a few ideas about how to integrate Swift logic with non-Swift code. We learned how to call Swift code from other languages with @_cdecl, but now we need to fill a few remaining gaps to build a modern non-Swift application on top of our Swift library. You can find the source code of these examples on GitHub. Asynchronous calls There’s no such thing as concurrency keywords in C. The only tool we have at disposal to return an async result is with a callback. To some extent, this reminds of completion handlers before Swift Concurrency entered the scene. However, Swift completion handlers are still closures, as in they can capture variables from the enclosing context. This is not the case for C callbacks, which are stateless pointers to function and therefore closer to static functions. The common pattern to mimic Swift completion handlers in C is by providing a callback context. Let’s go step by step. C types in arguments Say we want to get the result of this async function: public func swiftSlowResult() async throws -&gt; Int { ... // Code may throw here return 100 // Whatever works, we only care about the function signature } We cannot attach a @_cdecl attribute as long as we use features that don’t transpose to C, and async is one of them. Let’s take an intermediate step and wrap the function like the preconcurrency counterpart of many standard Swift APIs: public func swiftSlowResultWrapper( completion: @Sendable (Int?, Error?) -&gt; Void ) -&gt; Void { Task { do { let result = try await swiftSlowResult() completion(result, nil) } catch { completion(nil, error) } } } Cool, we got rid of async with the callback pattern. As we saw before, optionals and Error are other points of friction, so we introduce a user-defined error code enum, that is a plain int in C. All things considered, this is the first half-working version of our signature: enum SlowError: Int, Error { case foo = 10 case bar = 20 case unknown = 1000 } @_cdecl("swift_slow_result") public func swiftSlowResultWrapper( completion: @Sendable (Int, Int) -&gt; Void ) -&gt; Void { Task { do { let result = try await swiftSlowResult() // Error code 0 means success completion(result, 0) } catch let error as SlowError { // The first argument must be ignored on failure completion(0, error.rawValue) } catch { completion(0, SlowError.unknown.rawValue) } } } And a trivial example of its usage: #include &lt;stdio.h&gt; typedef enum { slow_error_none = 0, slow_error_foo = 10, slow_error_bar = 20, slow_error_unknown = 1000 } slow_error; static void my_result_callback(int result, slow_error error_code) { if (error_code != slow_error_none) { printf("Error: %d\n", error_code); return; } printf("Result: %d\n", result); } void invoke_swift_async_function() { /* ... */ swift_slow_result(my_result_callback); } Providing context Another problem to address is supporting captures behavior in the completion callback, because we probably want to do something useful upon completion. The de facto way to do it in C is, as mentioned before, by propagating a context: public func swiftSlowResultWrapper( ctx: UnsafeMutableRawPointer?, completion: @Sendable (UnsafeMutableRawPointer?, Int, Int) ) -&gt; Void { nonisolated(unsafe) let unsafeCtx = ctx Task { ... completion(unsafeCtx, result, errorCode) } } Unsafe and generic by nature, a context can be literally anything. Unsurprisingly, Swift UnsafeMutableRawPointer maps to void * in C. This is the second version of our C program interacting with Swift async code: /* Initialized elsewhere. */ typedef struct { int initial_value; void (*perform)(int); void (*report_failure)(slow_error); } complex_object; static void my_result_callback(void *ctx, int result, slow_error error_code) { complex_object *obj = (complex_object *)ctx; if (error_code != slow_error_none) { printf("Error: %d\n", error_code); obj-&gt;report_failure(error_code); return; } const int compound_result = obj-&gt;initial_value + result; printf("Compound result: %d\n", compound_result); /* Move on with our lives. */ obj-&gt;perform(compound_result); } /* We assume `obj` to be the context to act upon, and we expect it * to survive the synchronous function call. A pointer to a local * variable cannot be the context of an async function, because * the callback would eventually refer to a dangling pointer. */ void invoke_swift_async_function(complex_object *obj) { swift_slow_result(obj, my_result_callback); } Calling convention Nevertheless, the code lacks a final touch, also a very obscure one. As is, the signature still accepts a Swift closure (or ObjC block) for the completion argument, and this will inevitably lead to arcane compile issues or runtime crashes because of the wrong calling convention. We must enforce the @convention(c) attribute to restrict the input to C-style pointers to function, i.e., without captures: public func swiftSlowResultWrapper( ctx: UnsafeMutableRawPointer?, completion: @Sendable @convention(c) (UnsafeMutableRawPointer?, Int, Int) ) -&gt; Void { ... } I might be wrong, but as far as I can tell, the Swift compiler is unable to enforce the C calling convention in @_cdecl-enabled functions, because I believe it should. Event handling Another crux of a modern application is delivering and handling asynchronous events at scale. In the recent years, Combine was the most used tool for this purpose, and the SwiftUI onReceive() function certainly favored Combine publishers for emitting events. But… remember? Combine is not available outside Apple. Regardless of how we emit events, though, that is a concern of the Swift library. When we build the non-Swift side of the app, we only need a way to observe the events that the library emits. No magic involved, C is a simple language: we can register to events with a callback. The inherent difference with completion handlers, for example, is that an event callback will be a pointer to function that follows the application lifecycle. Emit the events from Swift Let’s start with a dumb events emitter. Here we emit events represented by integer codes from 1 to 3 at a 500 milliseconds interval. We use the same callback mechanism with an optional context. The consumer will start the events emitter and subscribe to it contextually. enum EventCode: Int, CaseIterable { case one = 1 case two case three } @_cdecl("swift_start_events_emitter") public func swiftStartEventsEmitter( ctx: UnsafeMutableRawPointer?, callback: (@Sendable @convention(c) (UnsafeMutableRawPointer?, Int) -&gt; Void)? ) { nonisolated(unsafe) let unsafeCtx = ctx Task { @Sendable in while true { guard let randomEvent = EventCode.allCases.randomElement() else { continue } callback?(unsafeCtx, randomEvent.rawValue) try await Task.sleep(for: .milliseconds(500)) } } } Subscribe to the events from C We reuse the same object from the previous example as the context, after adding a new method: typedef struct { ... void (*on_event)(int); } complex_object; So that the callback can be simply: static void my_event_callback(void *ctx, int event_code) { complex_object *obj = (complex_object *)ctx; obj-&gt;on_event(event_code); } When the application starts, we also start the emitter and register to it with our callback: swift_start_events_emitter(obj, my_event_callback); And that’s basically it. The on_event() method can then forward the event to other areas of the application as it pleases. Data exchange Okay, this can be really annoying, especially if your application is not a greenfield: exchanging strongly typed data across the Swift boundary. There’s a fundamental issue with any form of FFI, which is exchanging data between different programming languages. Keep in mind that returning opaque C pointers to Swift objects with Unmanaged is not comparable, because the C code is not meant to understand the contents of the memory. Such pointers are treated like integer identifiers. The trouble begins when we expect C code to understand the layout of Swift structures. The single source of truth The best way to deal with data is to pick a common format like JSON or Google Protocol Buffers (protobuf), that is, formats that are easy to encode and decode in any programming language. The approach is as follows: Define your application entities in an Intermediate Representation (IR). Autogenerate the Swift and non-Swift models from the IR, the single source of truth. Pass text (JSON) or binary data (protobuf) whenever a @_cdecl function requires a complex entity. Encode and decode entities to the native representation as needed. For what it’s worth, Quicktype and JSON Schemas can be a simpler IR alternative to protobuf. Example flow Below we define a Swift function taking an entity input encoded as JSON: /* The entity is autogenerated. */ public struct Entity: Codable { let num: Int let ch: Character } @_cdecl("swift_function") public func swift_function(obj: UnsafePointer&lt;CChar&gt;?) { guard let obj, let json = obj.data(using: .utf8) else { return } do { let entity = try JSONDecoder().decode(Entity.self, from: json) print("Entity: \(entity)") } catch { print("Failure: \(error)") } } This is how we invoke the Swift function in C: /* The entity and the JSON encoder are autogenerated. */ typedef struct { int num; char ch; } entity; extern char *encode_json(const entity *); func c_function() { entity obj = { 100, 'a' }; char *json = encode_json(obj); swift_function(json); free(json); } Limitations Unfortunately, the IR approach is unfriendly to apps that were written with a Swift-only mindset, and both Passepartout and Partout have a long-standing set of Codable domain entities. The switch to a JSON or protobuf codegen can be very challenging if you like me have relied on: RawRepresentable Enums with associated values Custom Codable implementations Other strongly typed semantics Because there are often no 1:1 counterparts in other languages. Especially if the entities are involved in a persistence layer, you must be extremely careful with any refactoring of the data model to avoid data loss or crashes. In my case, I tried to rewrite my domain with JSON Schema or swift-protobuf, but eventually chose to write my own codegen with SwiftSyntax to keep Swift as the source of truth. The codegen parses an IR from Swift and proceeds to transpile it to Kotlin for Android and C++ for Linux and Windows (later). It was tiring but safer because I didn’t need to change much of the application code, whereas a strict JSON domain would have been a potentially disruptive change. Better be safe than sorry. UPDATE 03/22/2026: Rather than generating the code myself, my strategy for Partout today is hybrid and consists of the steps below: Use Swift as the source of truth, and stay clear of any fancy way to customize serialization Make partout-codegen to parse a decent subset of Swift AST with SwiftSyntax into a restricted IR model Let the codegen convert the IR to an OpenAPI YAML schema Use the YAML with openapi-generator to generate the data model for any other programming language Unfortunately, I had to provide custom Codable implementations for any enum with associated values that was involved in serialized data. Kotlin sealed classes map way better to OpenAPI. All in all, the approach works very well to maintain data integrity across polyglot consumers of the library. Bottom line It’s been a long journey, but this article should wrap up everything you need to know to bring your Apple-only Swift/SwiftUI app to any other platform that Swift supports. It’s now time to make sense of all this yapping and build a real application with what we learned from Passepartout/Partout. Going forward, we’ll build a simple –but thorough– non-Swift application on top of a Swift library. The sample application will feature a native UI (both SwiftUI and non-Swift) interacting with Swift business logic through a C ABI layer. In other words, a scalable architecture to port a SwiftUI app to Linux, Android, and Windows. Last but not least, even friendly to AI agentic coding. See you in the next article.]]></summary></entry><entry><title type="html">Cross-platform Swift: Integration (pt. 1)</title><link href="https://davidederosa.com/cross-platform-swift/integration-part-one/" rel="alternate" type="text/html" title="Cross-platform Swift: Integration (pt. 1)" /><published>2025-12-22T00:00:00+01:00</published><updated>2025-12-22T00:00:00+01:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-integration-part-one</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/integration-part-one/"><![CDATA[<p>We are at a stage where Partout, our cross-platform Swift library:</p>

<ul>
  <li>Builds as a static library</li>
  <li>Emits a Swift module interface for use in other Swift code</li>
  <li>Comes with a few vendored dependencies as dynamic libraries (OpenSSL, WireGuard)</li>
  <li>Uses C for low-level routines, including OS frameworks or the NDK on Android</li>
  <li>Doesn’t use Apple frameworks, unless behind availability conditionals</li>
  <li>Doesn’t use Objective-C</li>
  <li>Depends on the Swift runtime</li>
</ul>

<p>It’s time to actually <em>use</em> the library, be it in an app or another library. I’ll skip the obvious case of using a Swift library in a Swift project, for which SwiftPM remains the recommended way to do it.</p>

<h2 id="cross-language-integration">Cross-language integration</h2>

<p>Earlier in the series, I think I mentioned how the C language is the typical denominator of multi-language interactions between binary programs. In other words, C is sort of the English language of programming languages, the midway where foreign languages meet, and in fact the preferred way to deal with <a href="https://en.wikipedia.org/wiki/Foreign_function_interface">FFI</a>. As a consequence, in order to use a Swift library in a non-Swift project, it must learn to speak C.</p>

<p>There are important design considerations to make before picking what to expose, but we could summarize them in a clear principle: your library must be able to behave like an API. C is a powerful yet very basic imperative language, so you’d better rethink your business logic in terms of simple types and function calls.</p>

<p>For example, a REST API translates quite well to a C interface:</p>

<ul>
  <li>Function name -&gt; HTTP Method + Path</li>
  <li>Function input -&gt; URL Components or HTTP Request</li>
  <li>Function output -&gt; HTTP Response</li>
  <li>Data types -&gt; JSON (untyped)</li>
</ul>

<p>More specifically, this Swift code:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">BankAccount</span> <span class="p">{</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">customerId</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">withdraw</span><span class="p">(</span><span class="nv">amount</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Could be exposed from a REST API like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /banks/&lt;customer_id&gt;
PUT /banks/&lt;customer_id&gt;/withdraw {"amount":...}
</code></pre></div></div>

<p>And linearized in a C API like:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Returns an opaque handle for subsequent calls</span>
<span class="kt">void</span> <span class="o">*</span><span class="nf">bank_account_find</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">customer_id</span><span class="p">);</span>
<span class="n">bool</span> <span class="nf">bank_account_withdraw</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">account</span><span class="p">,</span> <span class="kt">int</span> <span class="n">amount</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">bank_account_last_error</span><span class="p">();</span>
</code></pre></div></div>

<p>That is, with global, “static” vanilla functions. Swift abstractions <em>must</em> be left aside in this process.</p>

<h2 id="introducing-_cdecl">Introducing <code class="language-plaintext highlighter-rouge">@_cdecl</code></h2>

<p>Let’s follow up on the previous example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="k">var</span> <span class="nv">lastError</span><span class="p">:</span> <span class="kt">Error</span><span class="p">?</span>

<span class="kd">func</span> <span class="nf">bankAccountFind</span><span class="p">(</span><span class="nv">customerId</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">BankAccount</span><span class="p">?</span> <span class="p">{</span>
    <span class="c1">// Imagine a central database</span>
    <span class="kt">BankDatabase</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">find</span><span class="p">(</span><span class="n">customerId</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">bankAccountWithdraw</span><span class="p">(</span><span class="nv">account</span><span class="p">:</span> <span class="kt">BankAccount</span><span class="p">,</span> <span class="nv">amount</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">account</span><span class="o">.</span><span class="nf">withdraw</span><span class="p">(</span><span class="nv">amount</span><span class="p">:</span> <span class="n">amount</span><span class="p">)</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="n">lastError</span> <span class="o">=</span> <span class="n">error</span>
        <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">bankAccountLastError</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Error</span><span class="p">?</span> <span class="p">{</span>
    <span class="n">lastError</span>
<span class="p">}</span>
</code></pre></div></div>

<p>See how we expose Swift logic through global functions, with a minor notion of statefulness in <code class="language-plaintext highlighter-rouge">lastError</code>. State can be retained in different ways, e.g. with a context object, but the global variable is enough here for the sake of the example. The programming pattern is now close to the C imperative, but something is still off.</p>

<p>We must get rid of any Swift in the <em>public</em> interfaces, and there are several occurrences to fix in this code:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">String</code> in the <code class="language-plaintext highlighter-rouge">bankAccountFind</code> signature</li>
  <li><code class="language-plaintext highlighter-rouge">BankAccount</code> in the <code class="language-plaintext highlighter-rouge">bankAccountFind</code> and the <code class="language-plaintext highlighter-rouge">bankAccountWithdraw</code> signatures</li>
  <li><code class="language-plaintext highlighter-rouge">Error?</code> in the <code class="language-plaintext highlighter-rouge">bankAccountLastError</code> signature</li>
</ul>

<p>Our candidate C functions must only use C-compatible types, therefore:</p>

<ul>
  <li>Replace <code class="language-plaintext highlighter-rouge">String</code> with <code class="language-plaintext highlighter-rouge">UnsafePointer&lt;CChar&gt;</code> (i.e. <code class="language-plaintext highlighter-rouge">const char *</code>)</li>
  <li>Replace <code class="language-plaintext highlighter-rouge">BankAccount</code> with a generic pointer (i.e. <code class="language-plaintext highlighter-rouge">void *</code> or some unique identifier)</li>
  <li>Replace <code class="language-plaintext highlighter-rouge">Error</code> with an integer error code, for example</li>
</ul>

<p>Once we sort this out, we’re ready to make the functions publicly available, and we do that with the <strong><code class="language-plaintext highlighter-rouge">@_cdecl</code></strong> keyword:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">enum</span> <span class="kt">ErrorCode</span><span class="p">:</span> <span class="kt">Int</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">success</span>
    <span class="k">case</span> <span class="n">notFound</span>
    <span class="k">case</span> <span class="n">noMoney</span>
<span class="p">}</span>

<span class="kd">private</span> <span class="k">var</span> <span class="nv">lastErrorCode</span><span class="p">:</span> <span class="kt">ErrorCode</span><span class="p">?</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"bank_account_find"</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">bankAccountFind</span><span class="p">(</span><span class="n">customerId</span> <span class="nv">rawCustomerId</span><span class="p">:</span> <span class="kt">UnsafePointer</span><span class="o">&lt;</span><span class="kt">CChar</span><span class="o">&gt;</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">UnsafeMutableRawPointer</span><span class="p">?</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">rawCustomerId</span> <span class="k">else</span> <span class="p">{</span> <span class="nf">preconditionFailure</span><span class="p">()</span> <span class="p">}</span>
    <span class="k">let</span> <span class="nv">customerId</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">cString</span><span class="p">:</span> <span class="n">rawCustomerId</span><span class="p">)</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">existing</span> <span class="o">=</span> <span class="kt">BankDatabase</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">find</span><span class="p">(</span><span class="n">customerId</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">nil</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="nv">rawAccount</span> <span class="o">=</span> <span class="kt">Unmanaged</span><span class="o">.</span><span class="nf">passUnretained</span><span class="p">(</span><span class="n">existing</span><span class="p">)</span><span class="o">.</span><span class="nf">toOpaque</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">rawAccount</span>
<span class="p">}</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"bank_account_withdraw"</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">bankAccountWithdraw</span><span class="p">(</span><span class="n">account</span> <span class="nv">rawAccount</span><span class="p">:</span> <span class="kt">UnsafeMutableRawPointer</span><span class="p">?,</span> <span class="nv">amount</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">rawAccount</span> <span class="k">else</span> <span class="p">{</span> <span class="nf">preconditionFailure</span><span class="p">()</span> <span class="p">}</span>
    <span class="k">let</span> <span class="nv">account</span> <span class="o">=</span> <span class="kt">Unmanaged</span><span class="o">.</span><span class="nf">fromOpaque</span><span class="p">(</span><span class="n">rawAccount</span><span class="p">)</span><span class="o">.</span><span class="nf">takeUnretainedValue</span><span class="p">()</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="k">try</span> <span class="n">account</span><span class="o">.</span><span class="nf">withdraw</span><span class="p">(</span><span class="nv">amount</span><span class="p">:</span> <span class="n">amount</span><span class="p">)</span>
        <span class="n">lastErrorCode</span> <span class="o">=</span> <span class="kc">nil</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="n">lastErrorCode</span> <span class="o">=</span> <span class="o">.</span><span class="n">noMoney</span>
        <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">@_cdecl</span><span class="p">(</span><span class="s">"bank_account_last_error"</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">bankAccountLastError</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>
    <span class="p">(</span><span class="n">lastErrorCode</span> <span class="p">??</span> <span class="o">.</span><span class="n">success</span><span class="p">)</span><span class="o">.</span><span class="n">rawValue</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The optional arguments are there for maximum interop with C compilers. Standard C doesn’t provide nullability information, it’s something you must assert yourself. If you only use <code class="language-plaintext highlighter-rouge">clang</code>, though, you can leverage the <code class="language-plaintext highlighter-rouge">_Nonnull</code> and <code class="language-plaintext highlighter-rouge">_Nullable</code> qualifiers to avoid the unwraps and make your Swift code safer at compile-time.</p>

<p>Ultimately, you would bundle the library with a C header like the one we originally envisioned:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="o">*</span><span class="nf">bank_account_find</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">customer_id</span><span class="p">);</span>
<span class="n">bool</span> <span class="nf">bank_account_withdraw</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">account</span><span class="p">,</span> <span class="kt">int</span> <span class="n">amount</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">bank_account_last_error</span><span class="p">();</span>
</code></pre></div></div>

<p>And the external C consumer will call into Swift code without even knowing that it’s Swift. If one day you decide to switch from Swift to another language, be it Rust, Go, or C itself, the library consumers will not be affected. This is a nice-to-have for durable software.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>The way Swift supports FFI, i.e. communication with other programming languages, is with <code class="language-plaintext highlighter-rouge">@_cdecl</code> and C-compatible types. The ability for a Swift library to also exist as a pure C library allows it to be reusable across a vast number of environments without modifications. Swift consumers will certainly get more benefits and better ergonomics, but a well-designed C interface will cover any other non-Swift scenario that you can think of. More on this in the next article.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><category term="make" /><category term="cmake" /><summary type="html"><![CDATA[We are at a stage where Partout, our cross-platform Swift library: Builds as a static library Emits a Swift module interface for use in other Swift code Comes with a few vendored dependencies as dynamic libraries (OpenSSL, WireGuard) Uses C for low-level routines, including OS frameworks or the NDK on Android Doesn’t use Apple frameworks, unless behind availability conditionals Doesn’t use Objective-C Depends on the Swift runtime It’s time to actually use the library, be it in an app or another library. I’ll skip the obvious case of using a Swift library in a Swift project, for which SwiftPM remains the recommended way to do it. Cross-language integration Earlier in the series, I think I mentioned how the C language is the typical denominator of multi-language interactions between binary programs. In other words, C is sort of the English language of programming languages, the midway where foreign languages meet, and in fact the preferred way to deal with FFI. As a consequence, in order to use a Swift library in a non-Swift project, it must learn to speak C. There are important design considerations to make before picking what to expose, but we could summarize them in a clear principle: your library must be able to behave like an API. C is a powerful yet very basic imperative language, so you’d better rethink your business logic in terms of simple types and function calls. For example, a REST API translates quite well to a C interface: Function name -&gt; HTTP Method + Path Function input -&gt; URL Components or HTTP Request Function output -&gt; HTTP Response Data types -&gt; JSON (untyped) More specifically, this Swift code: final class BankAccount { init(customerId: String) { ... } func withdraw(amount: Int) throws { ... } } Could be exposed from a REST API like: GET /banks/&lt;customer_id&gt; PUT /banks/&lt;customer_id&gt;/withdraw {"amount":...} And linearized in a C API like: // Returns an opaque handle for subsequent calls void *bank_account_find(const char *customer_id); bool bank_account_withdraw(void *account, int amount); int bank_account_last_error(); That is, with global, “static” vanilla functions. Swift abstractions must be left aside in this process. Introducing @_cdecl Let’s follow up on the previous example: private var lastError: Error? func bankAccountFind(customerId: String) -&gt; BankAccount? { // Imagine a central database BankDatabase.shared.find(customerId) } func bankAccountWithdraw(account: BankAccount, amount: Int) -&gt; Bool { do { try account.withdraw(amount: amount) return true } catch { lastError = error return false } } func bankAccountLastError() -&gt; Error? { lastError } See how we expose Swift logic through global functions, with a minor notion of statefulness in lastError. State can be retained in different ways, e.g. with a context object, but the global variable is enough here for the sake of the example. The programming pattern is now close to the C imperative, but something is still off. We must get rid of any Swift in the public interfaces, and there are several occurrences to fix in this code: String in the bankAccountFind signature BankAccount in the bankAccountFind and the bankAccountWithdraw signatures Error? in the bankAccountLastError signature Our candidate C functions must only use C-compatible types, therefore: Replace String with UnsafePointer&lt;CChar&gt; (i.e. const char *) Replace BankAccount with a generic pointer (i.e. void * or some unique identifier) Replace Error with an integer error code, for example Once we sort this out, we’re ready to make the functions publicly available, and we do that with the @_cdecl keyword: private enum ErrorCode: Int { case success case notFound case noMoney } private var lastErrorCode: ErrorCode? @_cdecl("bank_account_find") func bankAccountFind(customerId rawCustomerId: UnsafePointer&lt;CChar&gt;?) -&gt; UnsafeMutableRawPointer? { guard let rawCustomerId else { preconditionFailure() } let customerId = String(cString: rawCustomerId) guard let existing = BankDatabase.shared.find(customerId) else { return nil } let rawAccount = Unmanaged.passUnretained(existing).toOpaque() return rawAccount } @_cdecl("bank_account_withdraw") func bankAccountWithdraw(account rawAccount: UnsafeMutableRawPointer?, amount: Int) -&gt; Bool { guard let rawAccount else { preconditionFailure() } let account = Unmanaged.fromOpaque(rawAccount).takeUnretainedValue() do { try account.withdraw(amount: amount) lastErrorCode = nil return true } catch { lastErrorCode = .noMoney return false } } @_cdecl("bank_account_last_error") func bankAccountLastError() -&gt; Int { (lastErrorCode ?? .success).rawValue } The optional arguments are there for maximum interop with C compilers. Standard C doesn’t provide nullability information, it’s something you must assert yourself. If you only use clang, though, you can leverage the _Nonnull and _Nullable qualifiers to avoid the unwraps and make your Swift code safer at compile-time. Ultimately, you would bundle the library with a C header like the one we originally envisioned: void *bank_account_find(const char *customer_id); bool bank_account_withdraw(void *account, int amount); int bank_account_last_error(); And the external C consumer will call into Swift code without even knowing that it’s Swift. If one day you decide to switch from Swift to another language, be it Rust, Go, or C itself, the library consumers will not be affected. This is a nice-to-have for durable software. Bottom line The way Swift supports FFI, i.e. communication with other programming languages, is with @_cdecl and C-compatible types. The ability for a Swift library to also exist as a pure C library allows it to be reusable across a vast number of environments without modifications. Swift consumers will certainly get more benefits and better ergonomics, but a well-designed C interface will cover any other non-Swift scenario that you can think of. More on this in the next article.]]></summary></entry><entry><title type="html">Cross-platform Swift: Build system (pt. 3)</title><link href="https://davidederosa.com/cross-platform-swift/build-system-part-three/" rel="alternate" type="text/html" title="Cross-platform Swift: Build system (pt. 3)" /><published>2025-12-21T00:00:00+01:00</published><updated>2025-12-21T00:00:00+01:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-build-system-part-three</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/build-system-part-three/"><![CDATA[<p>Since the <a href="/cross-platform-swift/build-system-part-two/">last article</a>, I’ve made significant progress about improving the build system of Passepartout/Partout and the layout of the outputs. That’s why I felt compelled to explore this part of the series a bit further before climbing up the software layers. Here I go through a few tricks that made the process both simpler and more efficient.</p>

<h2 id="trick-1-cmake-toolchains">Trick 1: CMake toolchains</h2>

<p>As mentioned before, SwiftPM still has its fair amount of quirks and limitations when it comes to building complex projects. The Android side was my main concern in that Swift SDKs are meant to work hand in hand with SwiftPM, thus making the CMake integration convoluted. Nevertheless, the good fellows <a href="https://forums.swift.org/t/cross-compiling-for-android-with-cmake/82924/">on the Swift forums</a> got me in the right direction: <strong>CMake toolchains</strong>.</p>

<p>A toolchain is a way to stuff in a single file everything you need to compile or cross-compile a project: the compiler, the compiler options, where to find the standard libraries, what to link by default and so on. At the end of the day, Swift SDKs tailor these settings in a way that is suitable for SwiftPM, but it didn’t take big additional efforts to adapt them to be a toolchain for use with CMake.</p>

<p>With some help from the environment variables, I could design toolchains that made my life <em>dramatically</em> easier to build Swift projects for Android and Linux. I truly encourage you to <a href="https://github.com/partout-io/partout/tree/master/toolchains">have a look at them</a> because they were a game-changer for me. They also forced me to understand at a deeper level how <code class="language-plaintext highlighter-rouge">swiftc</code> works and produces outputs under the hood. SwiftPM may be a bit too high-level for that matter.</p>

<h2 id="trick-2-static-linking">Trick 2: Static linking</h2>

<p>On Linux and Android, the Swift toolchain offers static variants of the standard libraries, and once again, I can’t deny that a major itch I have is the inconvenience of the Swift runtime. After encapsulating the annoyances of CMake into toolchains, however, I realized how quick it was at this point to link the Swift runtime statically. Basically, as simple as changing the <code class="language-plaintext highlighter-rouge">-resource-dir</code> in the <code class="language-plaintext highlighter-rouge">CMAKE_Swift_FLAGS</code> of the <a href="https://github.com/partout-io/partout/blob/master/toolchains/android.toolchain.cmake">toolchain file</a>. Check out the <a href="https://github.com/partout-io/partout/blob/master/scripts/build.sh">build script</a> to see what those variables represent.</p>

<p>Nevertheless, in case you don’t know how linking works in general, a static library is not something that lives on its own, whereas a dynamic library is much closer to a standalone executable. A static library is a set of “unanimated” binary code that a project can fetch and look through to assemble the final output, whereas a dynamic library is an output per se. This means that static libraries –and Swift is no exception–, cannot be part of another binary unless it’s a final output, be it an executable or a dynamic library.</p>

<p>I’ll make this subject clearer later on, when I’ll elaborate on the best strategies to make your Swift library:</p>

<ul>
  <li>A <em>single</em> output, with no Swift dependencies.</li>
  <li>100% agnostic of Swift to its consumers.</li>
  <li>Fully usable cross-platform and from <em>any</em> other programming language.</li>
</ul>

<h2 id="trick-3-understanding-swift-libraries">Trick 3: Understanding Swift libraries</h2>

<p>Passepartout doesn’t use Partout directly, it rather does through another Swift layer that sits in the middle to provide decoupling and app-specific behavior. At this point, it was time to properly understand how to <em>depend</em> on a Swift library (Partout) from another Swift library (the Passepartout logic). Again, SwiftPM makes this seamless, but CMake leaves you with no other option than digging down the rabbit hole. Let me give you a concrete example of what I’m talking about.</p>

<p>A Swift library, be it static or dynamic, is no different from a library written in any other language. Bear with my simplification, but a library is a binary file that exports resuable logic through a set of <em>symbols</em>, typically functions and variables/constants. See them as a big C file compiled together with your code, of which the header is the list of the exported symbols. The moment you <em>link</em> the library to your code, the symbols that your code uses are resolved within the library and merged together into the final outputs.</p>

<p>By this definition, it’s not straightforward how to tell a Swift library from a non-Swift library, because seen from the outside, libraries only expose a C-like interface. The way we enhance the integration of a Swift library is by making it export its <strong>modules</strong>. In CMake, you do it with the following flags:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set_target_properties(partout PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR}
    ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR}
    RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}
    # Swift modules are emitted with these properties
    Swift_MODULE_NAME "Partout"
    Swift_MODULE_DIRECTORY "${OUTPUT_DIR}/modules"
)
</code></pre></div></div>

<p>In this case:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">partout</code> is a Swift target producing a static library.</li>
  <li>The library is generated with the <code class="language-plaintext highlighter-rouge">.a</code> (Mac/*nix) or <code class="language-plaintext highlighter-rouge">.lib</code> (Windows) suffix in <code class="language-plaintext highlighter-rouge">${OUTPUT_DIR}</code>.</li>
  <li>Stemming from <code class="language-plaintext highlighter-rouge">Swift_MODULE_NAME</code>, the <code class="language-plaintext highlighter-rouge">Partout.swiftmodule</code> file is generated in <code class="language-plaintext highlighter-rouge">${OUTPUT_DIR}/modules</code>.</li>
</ul>

<p>Such file describes the metadata we need to use Partout with all the power of Swift, undoubtly more convenient than a dry C ABI. Somewhere else, like I do in Passepartout, the consumer of the Swift library will include the <code class="language-plaintext highlighter-rouge">/modules</code> directory in the headers search path:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>target_include_directories(passepartout_shared PRIVATE
    ${ABI_INCLUDE}
    ${OUTPUT_DIR}/partout
    ${OUTPUT_DIR}/partout/modules
)
</code></pre></div></div>

<p>Which is what will make the <code class="language-plaintext highlighter-rouge">import Partout</code> directive eventually available to Swift code! After a decade writing Swift, I wouldn’t be surprised if most Swift developers out there never had to do this manually.</p>

<h2 id="trick-4-non-default-imports">Trick 4: Non-default imports</h2>

<p>By the time you figure out the Swift linking model, you realize how important it is for interoperability that your library has the <em>smallest</em> public surface. The more internals you expose, the more you are prone to fragility and useless complexity. The habit of importing packages has to be taken cautiously when writing a library, because any import is literally a liability. Since the very beginning, the library developer should be aware of what to make public and act accordingly.</p>

<p>Why is that crucial? Because public symbols may end up in the Swift module output, the one we’ve just talked about in the previous chapter. Some examples:</p>

<ul>
  <li>If we use, say, a <code class="language-plaintext highlighter-rouge">CoreLocation</code> entity in a public function of our library, we’ll be tied to Apple forever.</li>
  <li>If we use an entity from an internal module in a public function, we’ll need to also export the internal module, and the internals are likely to change over time.</li>
  <li>If we use a C entity in a public function, we’ll need to also export the proper headers and modulemap.</li>
</ul>

<p>The way to avoid these in the first place had been around for a while, and it was <code class="language-plaintext highlighter-rouge">@_implementationOnly import</code>. The notation made sure that the imported symbols would never make it to the public interface of the library. Swift 6 eventually made this official and polished with the formal proposal of <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md">access level on imports</a>.</p>

<p>Internal and private imports are absolutely the best way to ensure that the public interface of a library is as small as possible, cross-platform, and easier to integrate for not dragging unnecessary dependencies.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>As long as CMake remains the superior tool, letting SwiftPM go was deeply beneficial in my fight against the complexity of the build system. There’s still minor work to do, but Partout has finally reached a point where, let alone the footprint, you can integrate it like any other C library. In the next article, I’ll show you how to use <code class="language-plaintext highlighter-rouge">@_cdecl</code> for that purpose.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><category term="make" /><category term="cmake" /><summary type="html"><![CDATA[Since the last article, I’ve made significant progress about improving the build system of Passepartout/Partout and the layout of the outputs. That’s why I felt compelled to explore this part of the series a bit further before climbing up the software layers. Here I go through a few tricks that made the process both simpler and more efficient. Trick 1: CMake toolchains As mentioned before, SwiftPM still has its fair amount of quirks and limitations when it comes to building complex projects. The Android side was my main concern in that Swift SDKs are meant to work hand in hand with SwiftPM, thus making the CMake integration convoluted. Nevertheless, the good fellows on the Swift forums got me in the right direction: CMake toolchains. A toolchain is a way to stuff in a single file everything you need to compile or cross-compile a project: the compiler, the compiler options, where to find the standard libraries, what to link by default and so on. At the end of the day, Swift SDKs tailor these settings in a way that is suitable for SwiftPM, but it didn’t take big additional efforts to adapt them to be a toolchain for use with CMake. With some help from the environment variables, I could design toolchains that made my life dramatically easier to build Swift projects for Android and Linux. I truly encourage you to have a look at them because they were a game-changer for me. They also forced me to understand at a deeper level how swiftc works and produces outputs under the hood. SwiftPM may be a bit too high-level for that matter. Trick 2: Static linking On Linux and Android, the Swift toolchain offers static variants of the standard libraries, and once again, I can’t deny that a major itch I have is the inconvenience of the Swift runtime. After encapsulating the annoyances of CMake into toolchains, however, I realized how quick it was at this point to link the Swift runtime statically. Basically, as simple as changing the -resource-dir in the CMAKE_Swift_FLAGS of the toolchain file. Check out the build script to see what those variables represent. Nevertheless, in case you don’t know how linking works in general, a static library is not something that lives on its own, whereas a dynamic library is much closer to a standalone executable. A static library is a set of “unanimated” binary code that a project can fetch and look through to assemble the final output, whereas a dynamic library is an output per se. This means that static libraries –and Swift is no exception–, cannot be part of another binary unless it’s a final output, be it an executable or a dynamic library. I’ll make this subject clearer later on, when I’ll elaborate on the best strategies to make your Swift library: A single output, with no Swift dependencies. 100% agnostic of Swift to its consumers. Fully usable cross-platform and from any other programming language. Trick 3: Understanding Swift libraries Passepartout doesn’t use Partout directly, it rather does through another Swift layer that sits in the middle to provide decoupling and app-specific behavior. At this point, it was time to properly understand how to depend on a Swift library (Partout) from another Swift library (the Passepartout logic). Again, SwiftPM makes this seamless, but CMake leaves you with no other option than digging down the rabbit hole. Let me give you a concrete example of what I’m talking about. A Swift library, be it static or dynamic, is no different from a library written in any other language. Bear with my simplification, but a library is a binary file that exports resuable logic through a set of symbols, typically functions and variables/constants. See them as a big C file compiled together with your code, of which the header is the list of the exported symbols. The moment you link the library to your code, the symbols that your code uses are resolved within the library and merged together into the final outputs. By this definition, it’s not straightforward how to tell a Swift library from a non-Swift library, because seen from the outside, libraries only expose a C-like interface. The way we enhance the integration of a Swift library is by making it export its modules. In CMake, you do it with the following flags: set_target_properties(partout PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR} RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR} # Swift modules are emitted with these properties Swift_MODULE_NAME "Partout" Swift_MODULE_DIRECTORY "${OUTPUT_DIR}/modules" ) In this case: partout is a Swift target producing a static library. The library is generated with the .a (Mac/*nix) or .lib (Windows) suffix in ${OUTPUT_DIR}. Stemming from Swift_MODULE_NAME, the Partout.swiftmodule file is generated in ${OUTPUT_DIR}/modules. Such file describes the metadata we need to use Partout with all the power of Swift, undoubtly more convenient than a dry C ABI. Somewhere else, like I do in Passepartout, the consumer of the Swift library will include the /modules directory in the headers search path: target_include_directories(passepartout_shared PRIVATE ${ABI_INCLUDE} ${OUTPUT_DIR}/partout ${OUTPUT_DIR}/partout/modules ) Which is what will make the import Partout directive eventually available to Swift code! After a decade writing Swift, I wouldn’t be surprised if most Swift developers out there never had to do this manually. Trick 4: Non-default imports By the time you figure out the Swift linking model, you realize how important it is for interoperability that your library has the smallest public surface. The more internals you expose, the more you are prone to fragility and useless complexity. The habit of importing packages has to be taken cautiously when writing a library, because any import is literally a liability. Since the very beginning, the library developer should be aware of what to make public and act accordingly. Why is that crucial? Because public symbols may end up in the Swift module output, the one we’ve just talked about in the previous chapter. Some examples: If we use, say, a CoreLocation entity in a public function of our library, we’ll be tied to Apple forever. If we use an entity from an internal module in a public function, we’ll need to also export the internal module, and the internals are likely to change over time. If we use a C entity in a public function, we’ll need to also export the proper headers and modulemap. The way to avoid these in the first place had been around for a while, and it was @_implementationOnly import. The notation made sure that the imported symbols would never make it to the public interface of the library. Swift 6 eventually made this official and polished with the formal proposal of access level on imports. Internal and private imports are absolutely the best way to ensure that the public interface of a library is as small as possible, cross-platform, and easier to integrate for not dragging unnecessary dependencies. Bottom line As long as CMake remains the superior tool, letting SwiftPM go was deeply beneficial in my fight against the complexity of the build system. There’s still minor work to do, but Partout has finally reached a point where, let alone the footprint, you can integrate it like any other C library. In the next article, I’ll show you how to use @_cdecl for that purpose.]]></summary></entry><entry><title type="html">Cross-platform Swift: Build system (pt. 2)</title><link href="https://davidederosa.com/cross-platform-swift/build-system-part-two/" rel="alternate" type="text/html" title="Cross-platform Swift: Build system (pt. 2)" /><published>2025-11-04T00:00:00+01:00</published><updated>2025-11-04T00:00:00+01:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-build-system-part-two</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/build-system-part-two/"><![CDATA[<h2 id="our-goals">Our goals</h2>

<p>Before heading back to the <a href="https://github.com/partout-io/partout">Partout</a> code, let’s focus on our ultimate goals:</p>

<ol>
  <li>Build as Swift module on Apple platforms</li>
  <li>Build as dynamic library on other platforms (.so on Linux/Android, .dll on Windows)</li>
</ol>

<p>Adding that:</p>

<ul>
  <li>Step 1 is natural with SwiftPM, except for how to manage dependencies</li>
  <li>Step 2 involves OpenSSL (C), WireGuard (Go), and Wintun (Windows DLL) at the time of writing, plus the proper Swift runtime for the OS</li>
</ul>

<h2 id="step-1-building-with-swiftpm">Step 1: Building with SwiftPM</h2>

<p>We know that SwiftPM can handle Swift, C, C++, and ObjC (Apple only) sources with the help of module maps. How do we include dependencies that don’t use neither of these languages, or depend on a custom build system? We bundle them as <em>binary libraries</em>.</p>

<p>We need to take a short break and spot a major annoyance of this: contrary to source files, binary libraries are tied to a CPU architecture. That’s why <a href="https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages">XCFramework</a> was introduced, because it’s the best way to bundle binary libraries for multiple architectures. With a XCFramework, SwiftPM will pick the right binaries for the target architecture. This is exactly how I integrated OpenSSL and WireGuard into Partout, with the <a href="https://github.com/partout-io/openssl-apple">openssl-apple</a> and <a href="https://github.com/partout-io/wg-go-apple">wg-go-apple</a> repositories respectively.</p>

<p>One may argue that OpenSSL, for example, is written in C and SwiftPM supports C, but a build system may go miles further than a programming language. Building OpenSSL is a <em>very</em> complex task, and distributing prebuilt binaries is a standard way to decouple from such complexity. In exchange, we accept the complexity of handling multiple architectures with an XCFramework.</p>

<p>The problem with XCFrameworks, though, is that they do not work on non-Apple platforms. <strong>Artifact bundles</strong> will solve this limitation, unless Swift 6.2 has already introduced them (I remember this was half-done in July 2025).</p>

<h2 id="step-2-building-with-cmake">Step 2: Building with CMake</h2>

<p>Needless to say, SwiftPM is not mature enough on non-Apple. In order to have consistent builds of Swift code with binary dependencies, we’ll have resort to one of the least loved build tools around: <a href="https://cmake.org/">CMake</a>.</p>

<p>You know the say “love and hate”. Well, the thought of CMake generally leans towards “hate and hate”, because I haven’t heard a single developer that enjoys using it. In all fairness, the low popularity of CMake might stem from being the typical build system of C++ projects. I mean, there could be a bias, but we don’t care here, because CMake is somewhat the <em>only</em> way to accomplish what we need.</p>

<h3 id="why-cmake">Why CMake?</h3>

<p>CMake is not a replacement for Make, it’s rather a “generator of makefiles”, with a makefile being not necessarily the <code class="language-plaintext highlighter-rouge">Makefile</code> file, but the configuration file of a <em>build system</em>. The main feature of CMake is the ability of coordinating multiple projects written in different languages and/or built with different build systems. Since we are assembling a Frankenstein project made of Swift, C (OpenSSL), Go (WireGuard), and other prebuilt binary libraries (Wintun), CMake comes to our rescue.</p>

<p>For the record, CMake supports Swift code only through the <a href="https://ninja-build.org/"><code class="language-plaintext highlighter-rouge">ninja</code></a> generator.</p>

<h3 id="the-layout-of-our-superproject">The layout of our <em>superproject</em></h3>

<p>The first, comprehensive approach is to build our Swift library entirely with CMake. This is the most solid approach.</p>

<p>Partout is built in four steps:</p>

<ol>
  <li>The vendors are built as binary libraries. OpenSSL produces <code class="language-plaintext highlighter-rouge">libcrypto</code> and <code class="language-plaintext highlighter-rouge">libssl</code>, WireGuard produces <code class="language-plaintext highlighter-rouge">libwg-go</code>.</li>
  <li>The C/C++ code of our package is compiled as a monolith. Manual module maps must be exposed in the headers search paths for Swift interop with C/C++ code (SwiftPM does this automatically).</li>
  <li>The Swift code of our package is compiled altogether, thus losing any notion of SwiftPM sub-targets. C modules are available to Swift through step 2.</li>
  <li>All the outputs are linked together into the final dynamic library.</li>
</ol>

<p>Without delving into the very details of this complex task, we want to use a single CMake <em>superproject</em> to build our Swift project, Partout, plus all its dependencies. The superproject will treat both our Swift/C code and the vendors as opaque dependencies, i.e. <em>subprojects</em>. This way, CMake can ignore the internals of how any dependency is built.</p>

<p>Assume that each vendored third party comes with its own build system, and we orchestrate them all in a root <a href="https://github.com/partout-io/partout/blob/master/CMakeLists.txt"><code class="language-plaintext highlighter-rouge">CMakeLists.txt</code></a>. The root configuration includes one CMake file (*.cmake) for each vendor, as you can see in the <a href="https://github.com/partout-io/partout/tree/master/vendors"><code class="language-plaintext highlighter-rouge">vendors</code></a> directory of Partout. The .cmake files instruct the root configuration how to build each library as a subproject, and where to find the outputs, typically in the form of static or dynamic libraries.</p>

<p>Our package is also treated <a href="https://github.com/partout-io/partout/blob/master/Sources/CMakeLists.txt">as a subproject</a>, only as a monolith. By monolith, I mean that we give up on the granularity of SwiftPM targets and compile the Swift sources altogether. By doing this, the internal target imports will have to omitted, and we do that with the <code class="language-plaintext highlighter-rouge">PARTOUT_MONOLITH</code> symbol <a href="https://github.com/partout-io/partout/blob/9deda7b34107567ff4b76f53947ca718fe94956e/Sources/partout.cmake#L8">in the CMake project</a> and <a href="https://github.com/partout-io/partout/blob/9deda7b34107567ff4b76f53947ca718fe94956e/Sources/PartoutOS/Apple/AppleJavaScriptEngine.swift#L6">in the Swift code</a>.</p>

<p>You find the entry point of this long process in <a href="https://github.com/partout-io/partout/blob/master/scripts/build.sh"><code class="language-plaintext highlighter-rouge">scripts/build.sh</code></a>.</p>

<h3 id="hybrid-swiftpmcmake">Hybrid SwiftPM/CMake</h3>

<p>The full CMake approach works on all platforms except one: Android.</p>

<p>Why is that? Because, unless you want to rebuild the entire Swift toolchain for Android, the common way to target the Android platform is <a href="https://github.com/finagolfin/swift-android-sdk">through a Swift SDK</a>, and Swift SDKs only work with SwiftPM. On the other hand, building Swift for Android with CMake is still flaky and painful, so I’ll describe what we’re left with.</p>

<p>I chose to go with a hybrid build system where:</p>

<ul>
  <li>The Partout code (Swift/C) is built with SwiftPM, targeting the Swift Android SDK and the Android NDK.</li>
  <li>The vendored libraries are still built with CMake, this time for Android (<code class="language-plaintext highlighter-rouge">PP_BUILD_FOR_ANDROID</code> in CMake).</li>
  <li>SwiftPM depends on the CMake-built binaries via <code class="language-plaintext highlighter-rouge">.unsafeFlags</code> and generates a <code class="language-plaintext highlighter-rouge">.dynamic</code> library (<code class="language-plaintext highlighter-rouge">libpartout.so</code>).</li>
</ul>

<p>The way I accomplish this is with a <a href="https://github.com/partout-io/partout/blob/master/Package.swift">highly flexible Package.swift</a>. A dynamic manifest helps a lot when you need to overcome the occasional limitations of SwiftPM, and the <code class="language-plaintext highlighter-rouge">Package.swift</code> of Partout handles, among other things, OS conditionals and environment variables as the build input.</p>

<p>For example:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">PP_BUILD_OS</code> works around the limitation of <code class="language-plaintext highlighter-rouge">#if os(Android)</code>, because the condition fails unless a full Android toolchain is used.</li>
  <li><code class="language-plaintext highlighter-rouge">PP_BUILD_CMAKE_OUTPUT</code> tells the SwiftPM build where to find the arch-specific outputs of CMake for use with <code class="language-plaintext highlighter-rouge">.unsafeFlags</code> (the well-known <code class="language-plaintext highlighter-rouge">-I</code>, <code class="language-plaintext highlighter-rouge">-L</code>, and <code class="language-plaintext highlighter-rouge">-l</code> flags of the compiler/linker).</li>
</ul>

<p>You can learn more about the whole process in <a href="https://github.com/partout-io/partout/blob/master/scripts/build-android.sh"><code class="language-plaintext highlighter-rouge">scripts/build-android.sh</code></a>.</p>

<h3 id="distribution">Distribution</h3>

<p>Will your Swift project work with <code class="language-plaintext highlighter-rouge">libpartout.so|.dylib|.dll</code> alone? Of course not. One reason is obvious, and it’s because you need to bundle the OpenSSL/WireGuard binaries too. The other reason is that your end-user will lack the <strong>Swift runtime</strong>, and this is still kind of a big deal.</p>

<p>As I mentioned in earlier posts, the Swift runtime is <em>big</em>, or <em>huge</em> if you use Foundation like nearly every Swift programmer does. It’s not even the worst part, as the one I dislike the most is that distributing the Swift runtime is a heavily manual process, and even the standard lib is made of dozens of files. Use <code class="language-plaintext highlighter-rouge">otool</code> (macOS) or <code class="language-plaintext highlighter-rouge">ldd</code> (Linux) on your output to learn what libraries you need, and beware that your app will crash on launch if it lacks any of them. In my experience, bundling <em>more</em> dependencies than necessary may also lead to runtime crashes.</p>

<p>The <code class="language-plaintext highlighter-rouge">-static-swift-stdlib</code> flag exists to embed the runtime and mitigate the issue, but it seems <a href="https://github.com/swiftlang/swift-package-manager/issues/8198">it doesn’t work properly for dynamic libraries</a>. It doesn’t help with Foundation either because it’s not part of the standard Swift library.</p>

<p>Long story short, <code class="language-plaintext highlighter-rouge">libpartout.so</code> must be distributed side by side with the CMake binaries and the whole Swift runtime for the target architecture. You should find the runtime libraries for your current architecture at:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$SWIFT_HOME/toolchains/&lt;swift_version&gt;/usr/lib/swift/&lt;platform_name&gt;
</code></pre></div></div>

<p>With an additional dependency on <a href="https://github.com/swiftlang/swift/blob/main/docs/Android.md#3-deploying-the-build-products-to-the-device"><code class="language-plaintext highlighter-rouge">libc++_shared.so</code> for Android</a>.</p>

<p>Anyway, this Linux output should explain better what I’m talking about:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldd src/passepartout/submodules/partout/.build/debug/libpartout.so 
	linux-vdso.so.1 (0x0000ec74cd2dc000)
	libswiftSwiftOnoneSupport.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftSwiftOnoneSupport.so (0x0000ec74ccc40000)
	libswiftCore.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftCore.so (0x0000ec74cc5a0000)
	libswift_Concurrency.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_Concurrency.so (0x0000ec74cc4f0000)
	libswift_StringProcessing.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_StringProcessing.so (0x0000ec74cc430000)
	libswift_RegexParser.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_RegexParser.so (0x0000ec74cc310000)
	libBlocksRuntime.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libBlocksRuntime.so (0x0000ec74cc2e0000)
	libdispatch.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libdispatch.so (0x0000ec74cc260000)
	libswiftDispatch.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftDispatch.so (0x0000ec74cc210000)
	libssl.so.3 =&gt; /lib/aarch64-linux-gnu/libssl.so.3 (0x0000ec74cc0f0000)
	libcrypto.so.3 =&gt; /lib/aarch64-linux-gnu/libcrypto.so.3 (0x0000ec74cbb60000)
	libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6 (0x0000ec74cb990000)
	/lib/ld-linux-aarch64.so.1 (0x0000ec74cd2a0000)
	libstdc++.so.6 =&gt; /lib/aarch64-linux-gnu/libstdc++.so.6 (0x0000ec74cb6f0000)
	libm.so.6 =&gt; /lib/aarch64-linux-gnu/libm.so.6 (0x0000ec74cb630000)
	libgcc_s.so.1 =&gt; /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ec74cb5f0000)
	libswiftGlibc.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftGlibc.so (0x0000ec74cb5c0000)
	libz.so.1 =&gt; /lib/aarch64-linux-gnu/libz.so.1 (0x0000ec74cb580000)
	libzstd.so.1 =&gt; /lib/aarch64-linux-gnu/libzstd.so.1 (0x0000ec74cb4c0000)
</code></pre></div></div>

<h2 id="bottom-line">Bottom line</h2>

<p>I assumed the readers to be familiar with build systems and CMake in particular, as the subject is vast and goes way beyond the scope of my article. I rather wanted to describe the design that worked for me to build a <em>polyglot</em> Swift library not only for non-Apple platforms, but also with multi-language, real-world dependencies.</p>

<p>This is the real novelty, the one I’m so excited about, and the one that you would have a very hard time finding examples about. I’m bringing up proof that all this stuff <em>works</em> in Swift as Partout, the subject of this series, is not a toy project for the sake of a tutorial, but <strong>software in use by hundreds of thousands of users</strong> every day for streaming, privacy, remote work, and VPNs in general. Think about it for the bright future of the Swift language.</p>

<p>In the next article, we’ll go through the integration of Partout in both Swift and non-Swift applications, with my <a href="https://passepartoutvpn.app">Passepartout</a> app being the living example of it.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><category term="make" /><category term="cmake" /><summary type="html"><![CDATA[Our goals Before heading back to the Partout code, let’s focus on our ultimate goals: Build as Swift module on Apple platforms Build as dynamic library on other platforms (.so on Linux/Android, .dll on Windows) Adding that: Step 1 is natural with SwiftPM, except for how to manage dependencies Step 2 involves OpenSSL (C), WireGuard (Go), and Wintun (Windows DLL) at the time of writing, plus the proper Swift runtime for the OS Step 1: Building with SwiftPM We know that SwiftPM can handle Swift, C, C++, and ObjC (Apple only) sources with the help of module maps. How do we include dependencies that don’t use neither of these languages, or depend on a custom build system? We bundle them as binary libraries. We need to take a short break and spot a major annoyance of this: contrary to source files, binary libraries are tied to a CPU architecture. That’s why XCFramework was introduced, because it’s the best way to bundle binary libraries for multiple architectures. With a XCFramework, SwiftPM will pick the right binaries for the target architecture. This is exactly how I integrated OpenSSL and WireGuard into Partout, with the openssl-apple and wg-go-apple repositories respectively. One may argue that OpenSSL, for example, is written in C and SwiftPM supports C, but a build system may go miles further than a programming language. Building OpenSSL is a very complex task, and distributing prebuilt binaries is a standard way to decouple from such complexity. In exchange, we accept the complexity of handling multiple architectures with an XCFramework. The problem with XCFrameworks, though, is that they do not work on non-Apple platforms. Artifact bundles will solve this limitation, unless Swift 6.2 has already introduced them (I remember this was half-done in July 2025). Step 2: Building with CMake Needless to say, SwiftPM is not mature enough on non-Apple. In order to have consistent builds of Swift code with binary dependencies, we’ll have resort to one of the least loved build tools around: CMake. You know the say “love and hate”. Well, the thought of CMake generally leans towards “hate and hate”, because I haven’t heard a single developer that enjoys using it. In all fairness, the low popularity of CMake might stem from being the typical build system of C++ projects. I mean, there could be a bias, but we don’t care here, because CMake is somewhat the only way to accomplish what we need. Why CMake? CMake is not a replacement for Make, it’s rather a “generator of makefiles”, with a makefile being not necessarily the Makefile file, but the configuration file of a build system. The main feature of CMake is the ability of coordinating multiple projects written in different languages and/or built with different build systems. Since we are assembling a Frankenstein project made of Swift, C (OpenSSL), Go (WireGuard), and other prebuilt binary libraries (Wintun), CMake comes to our rescue. For the record, CMake supports Swift code only through the ninja generator. The layout of our superproject The first, comprehensive approach is to build our Swift library entirely with CMake. This is the most solid approach. Partout is built in four steps: The vendors are built as binary libraries. OpenSSL produces libcrypto and libssl, WireGuard produces libwg-go. The C/C++ code of our package is compiled as a monolith. Manual module maps must be exposed in the headers search paths for Swift interop with C/C++ code (SwiftPM does this automatically). The Swift code of our package is compiled altogether, thus losing any notion of SwiftPM sub-targets. C modules are available to Swift through step 2. All the outputs are linked together into the final dynamic library. Without delving into the very details of this complex task, we want to use a single CMake superproject to build our Swift project, Partout, plus all its dependencies. The superproject will treat both our Swift/C code and the vendors as opaque dependencies, i.e. subprojects. This way, CMake can ignore the internals of how any dependency is built. Assume that each vendored third party comes with its own build system, and we orchestrate them all in a root CMakeLists.txt. The root configuration includes one CMake file (*.cmake) for each vendor, as you can see in the vendors directory of Partout. The .cmake files instruct the root configuration how to build each library as a subproject, and where to find the outputs, typically in the form of static or dynamic libraries. Our package is also treated as a subproject, only as a monolith. By monolith, I mean that we give up on the granularity of SwiftPM targets and compile the Swift sources altogether. By doing this, the internal target imports will have to omitted, and we do that with the PARTOUT_MONOLITH symbol in the CMake project and in the Swift code. You find the entry point of this long process in scripts/build.sh. Hybrid SwiftPM/CMake The full CMake approach works on all platforms except one: Android. Why is that? Because, unless you want to rebuild the entire Swift toolchain for Android, the common way to target the Android platform is through a Swift SDK, and Swift SDKs only work with SwiftPM. On the other hand, building Swift for Android with CMake is still flaky and painful, so I’ll describe what we’re left with. I chose to go with a hybrid build system where: The Partout code (Swift/C) is built with SwiftPM, targeting the Swift Android SDK and the Android NDK. The vendored libraries are still built with CMake, this time for Android (PP_BUILD_FOR_ANDROID in CMake). SwiftPM depends on the CMake-built binaries via .unsafeFlags and generates a .dynamic library (libpartout.so). The way I accomplish this is with a highly flexible Package.swift. A dynamic manifest helps a lot when you need to overcome the occasional limitations of SwiftPM, and the Package.swift of Partout handles, among other things, OS conditionals and environment variables as the build input. For example: PP_BUILD_OS works around the limitation of #if os(Android), because the condition fails unless a full Android toolchain is used. PP_BUILD_CMAKE_OUTPUT tells the SwiftPM build where to find the arch-specific outputs of CMake for use with .unsafeFlags (the well-known -I, -L, and -l flags of the compiler/linker). You can learn more about the whole process in scripts/build-android.sh. Distribution Will your Swift project work with libpartout.so|.dylib|.dll alone? Of course not. One reason is obvious, and it’s because you need to bundle the OpenSSL/WireGuard binaries too. The other reason is that your end-user will lack the Swift runtime, and this is still kind of a big deal. As I mentioned in earlier posts, the Swift runtime is big, or huge if you use Foundation like nearly every Swift programmer does. It’s not even the worst part, as the one I dislike the most is that distributing the Swift runtime is a heavily manual process, and even the standard lib is made of dozens of files. Use otool (macOS) or ldd (Linux) on your output to learn what libraries you need, and beware that your app will crash on launch if it lacks any of them. In my experience, bundling more dependencies than necessary may also lead to runtime crashes. The -static-swift-stdlib flag exists to embed the runtime and mitigate the issue, but it seems it doesn’t work properly for dynamic libraries. It doesn’t help with Foundation either because it’s not part of the standard Swift library. Long story short, libpartout.so must be distributed side by side with the CMake binaries and the whole Swift runtime for the target architecture. You should find the runtime libraries for your current architecture at: $SWIFT_HOME/toolchains/&lt;swift_version&gt;/usr/lib/swift/&lt;platform_name&gt; With an additional dependency on libc++_shared.so for Android. Anyway, this Linux output should explain better what I’m talking about: ldd src/passepartout/submodules/partout/.build/debug/libpartout.so linux-vdso.so.1 (0x0000ec74cd2dc000) libswiftSwiftOnoneSupport.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftSwiftOnoneSupport.so (0x0000ec74ccc40000) libswiftCore.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftCore.so (0x0000ec74cc5a0000) libswift_Concurrency.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_Concurrency.so (0x0000ec74cc4f0000) libswift_StringProcessing.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_StringProcessing.so (0x0000ec74cc430000) libswift_RegexParser.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_RegexParser.so (0x0000ec74cc310000) libBlocksRuntime.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libBlocksRuntime.so (0x0000ec74cc2e0000) libdispatch.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libdispatch.so (0x0000ec74cc260000) libswiftDispatch.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftDispatch.so (0x0000ec74cc210000) libssl.so.3 =&gt; /lib/aarch64-linux-gnu/libssl.so.3 (0x0000ec74cc0f0000) libcrypto.so.3 =&gt; /lib/aarch64-linux-gnu/libcrypto.so.3 (0x0000ec74cbb60000) libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6 (0x0000ec74cb990000) /lib/ld-linux-aarch64.so.1 (0x0000ec74cd2a0000) libstdc++.so.6 =&gt; /lib/aarch64-linux-gnu/libstdc++.so.6 (0x0000ec74cb6f0000) libm.so.6 =&gt; /lib/aarch64-linux-gnu/libm.so.6 (0x0000ec74cb630000) libgcc_s.so.1 =&gt; /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ec74cb5f0000) libswiftGlibc.so =&gt; /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftGlibc.so (0x0000ec74cb5c0000) libz.so.1 =&gt; /lib/aarch64-linux-gnu/libz.so.1 (0x0000ec74cb580000) libzstd.so.1 =&gt; /lib/aarch64-linux-gnu/libzstd.so.1 (0x0000ec74cb4c0000) Bottom line I assumed the readers to be familiar with build systems and CMake in particular, as the subject is vast and goes way beyond the scope of my article. I rather wanted to describe the design that worked for me to build a polyglot Swift library not only for non-Apple platforms, but also with multi-language, real-world dependencies. This is the real novelty, the one I’m so excited about, and the one that you would have a very hard time finding examples about. I’m bringing up proof that all this stuff works in Swift as Partout, the subject of this series, is not a toy project for the sake of a tutorial, but software in use by hundreds of thousands of users every day for streaming, privacy, remote work, and VPNs in general. Think about it for the bright future of the Swift language. In the next article, we’ll go through the integration of Partout in both Swift and non-Swift applications, with my Passepartout app being the living example of it.]]></summary></entry><entry><title type="html">Cross-platform Swift: Build system (pt. 1)</title><link href="https://davidederosa.com/cross-platform-swift/build-system-part-one/" rel="alternate" type="text/html" title="Cross-platform Swift: Build system (pt. 1)" /><published>2025-09-07T00:00:00+02:00</published><updated>2025-09-07T00:00:00+02:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-build-system-part-one</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/build-system-part-one/"><![CDATA[<p>There is a reason why it took me two months since the last post, and ironically it’s the very subject of this writing. I hate to say, but the build tools are the most painful and work-in-progress aspect of Swift, at least at the time of writing. Brace yourself.</p>

<h2 id="swiftpm-is-just-not-ready">SwiftPM is just not ready</h2>

<p>This post risks having a negative vibe, so I want to discuss solutions before problems. The first advice I can give if you want to build consistently across platforms is to <strong>avoid SwiftPM</strong>, except for Swift Testing. It’s just not fit for the role yet. XCFrameworks only work on Apple, and binary artifact bundles are on their way, but haven’t been released yet. If you need something that works <em>today</em>, get familiar with <code class="language-plaintext highlighter-rouge">swiftc</code> and <code class="language-plaintext highlighter-rouge">clang</code>, and switch to <strong>CMake with ninja</strong>. With CMake in place, Swift gains back the freedom and power it deserves. You take full control of what’s going on, and this will help you understand and fix the build issues that you’re bound to encounter.</p>

<p>At this point, you may come to realize how SwiftPM could lead you to a complex source hierarchy. The highly granular dependency model encouraged by the manifest is a neat abstraction, and works very well for modular Swift-only systems. But when you have to account for C/C++ targets, modulemaps, DocC, and external non-Swift libraries, it makes you want to jump off the nearest cliff without even a goodbye letter. In that case, making your source hierarchy flat and monolithic will make your life much easier. Two targets: one for Swift files, and one for C/C++ with a single modulemap.</p>

<p>Unfortunately, there’s one thing you might want SwiftPM for: the <a href="https://github.com/swiftlang/swift-sdk-generator">Swift SDKs</a>. For what it’s worth, I use a <a href="https://github.com/finagolfin/swift-android-sdk">Swift SDK for my Android builds</a>, and I have no idea if there’s a place for it in a CMake build, but I kind of doubt it. Nevertheless, given that Passepartout –and other consumers– still need Partout as a SwiftPM dependency, I’m okay with keeping both the Package.swift and CMake around for some time.</p>

<h2 id="the-swift-runtime">The Swift runtime</h2>

<p>There are two common ways to compile Swift files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">swiftc</code></li>
  <li><code class="language-plaintext highlighter-rouge">swift build</code> (Swift Package Manager)</li>
</ul>

<p>The majority of Swift programmers, hardly use or tweak <code class="language-plaintext highlighter-rouge">swiftc</code> directly, which is similar to how one would use the <code class="language-plaintext highlighter-rouge">clang</code> or <code class="language-plaintext highlighter-rouge">gcc</code> C/C++ compilers. Over the years, the Xcode integration and the fast turnaround of SwiftPM execution from the command line made <code class="language-plaintext highlighter-rouge">swift build</code> the natural way to compile and link Swift projects, both standalone and inside Xcode.</p>

<p>This is all great, until it’s not. The lack of insight into what SwiftPM exactly does may be very problematic when we face a compiler or linker issue. However, pure Swift projects hardly hit major build issues, so don’t be surprised if the average iOS programmer doesn’t know how a Swift executable is generated or what it needs to run on a machine, because Apple devices come with the <strong>Swift runtime</strong> preinstalled.</p>

<p>The language “runtime” is a set of libraries that an executable requires to run on a specific architecture. Let me explain why I find this a serious blocker for Swift adoption.</p>

<h2 id="binary-distribution-is-impractical">Binary distribution is impractical</h2>

<p>Let’s pick Linux, for example, which on a typical modern desktop may come in arm64 or x86_64 flavors. Not only does this imply two runtimes to deal with for distributing our binaries, the Swift runtime is also split into <em>dozens</em> of files. If we close an eye on the footprint, this is still annoying, but less of a problem on platforms where the executable model is self-contained (iOS, macOS, Android, …). You could distribute a self-contained folder/installer on Linux, but it always feels off on a system where libraries are typically shared in <code class="language-plaintext highlighter-rouge">/usr/lib</code> for everybody.</p>

<p>Installing the Swift runtime with a package manager seems like a smoother solution, and saves us from the burden of manually bundling and maintaining the binaries for each architecture. To my knowledge, <a href="https://launchpad.net/ubuntu/+source/swiftlang">apt has a libswiftlang package</a>. I don’t know how maintained it is, because the apt package is at 6.0.3 with the latest toolchain being at 6.1.2. Not a 100% healthy sign, but not worrying either.</p>

<p>Still, this introduces a dependency, and forces you to either distribute your executable through a package manager, or include manual steps in order to fetch the proper Swift runtime, for the proper platform, and for the proper architecture. In that regard, I don’t like that <a href="https://swift.org">swift.org</a> doesn’t offer direct links to download runtime-only installers, like Java used to do with the Java Runtime Environment (JRE).</p>

<p>A more convenient solution for Linux was <a href="https://forums.swift.org/t/static-linking-on-linux-in-swift-5-3-1/41989">introduced in Swift 5.3.1 with static linking</a> in an attempt to match the self-contained approach of Golang, which is the state of the art for native multiplatform applications. Static linking to the stdlib is not as straightforward as running <code class="language-plaintext highlighter-rouge">go build</code>, but still.</p>

<h2 id="the-loneliness-of-the-other-platforms">The loneliness of the other platforms</h2>

<p>All things considered, the friction remains <em>very</em> real. Now, if this already sounds complicated, what if I told you that Linux is the <em>best</em> supported platform after Apple’s?</p>

<p>The weight of Windows and Android is on the shoulders of a few kind individuals, whose progress can be followed on the official Swift forums. For example, hard work is being done to bring <a href="https://forums.swift.org/t/upcoming-changes-to-windows-swift-sdks/81313">static linking on the Windows runtime</a> as of Swift 6.2 (unreleased yet), and a few months ago, <a href="https://forums.swift.org/t/announcing-the-android-workgroup/80666">an Android workgroup was made official</a>. Until then, Android development was mostly pushed by volunteers.</p>

<p>I’m all about supporting the maintainers of these exciting initiatives, and that’s why I took this winding path to experiment myself, but if you need something that works <em>right away</em> for building production software in Swift, you’d better assume that you’re on your own. There’s still work to be done to make the experience acceptable for the public domain, and Android builds in particular take <em>a lot</em> of manual steps, or way more than a lot.</p>

<p>At the end of the day, it depends on your goals. Personally, I was thrilled when I made Partout connect to a VPN on Android, with Linux syscalls over a Swift codebase that talks to Kotlin via JNI (!). That chill of “could this even work?”, because nobody had done it before. You hardly get that feeling with battle-tested languages, and if you like novelties, Swift is a greenfield that I would encourage you to explore and support.</p>

<h2 id="the-footprint-is-huge-by-default">The footprint is huge by default</h2>

<p>In a former article, I mentioned that <a href="/cross-platform-swift/core-libraries/">Foundation is not part of the standard library</a>, as in it’s not part of the essential runtime (libSwiftCore). In terms of size, the core runtime is not excessively concerning at around 10-15MB total. In fact, I’m way more annoyed by the fact that it’s made of tens of files. Well, add Foundation to the mix, and your runtime spikes to almost 100MB, with ICU contributing to around 40MB of it. And Foundation is hard to avoid. Frankly, that’s <em>insane</em>, but I still want to believe that this is the kind legacy from the Apple era of Swift. Let’s not forget that Foundation is bundled with any Apple OS.</p>

<p>My advice? If you’re starting from scratch, and footprint is a concern, never import Foundation, or maybe consider not using Swift at all. Go is more mature for the purpose of standalone outputs, easy to build, ubiquitous. If you want to stick with Swift or port your existing codebase to non-Apple, instead, consider dropping Foundation and reimplementing the parts that you need. With careful analysis, you might conclude that, despite its convenience, you don’t need it to the point of justifying a 100MB executable.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>So, it’s 2025 and you want to make native cross-platform apps or libraries. Hard truth: if you want to make them <em>today</em>, Go is a wiser choice. If you want to use Swift, for now, prefer CMake over SwiftPM. Get your feet wet with the raw tooling to understand how Swift libraries are generated. Don’t be like me, keep a flat source hierarchy from the beginning.</p>

<p>In the next article, we’ll go through real examples of how I managed to build consistent outputs with SwiftPM/CMake on macOS, Linux, Windows, and even Android.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><category term="make" /><category term="cmake" /><summary type="html"><![CDATA[There is a reason why it took me two months since the last post, and ironically it’s the very subject of this writing. I hate to say, but the build tools are the most painful and work-in-progress aspect of Swift, at least at the time of writing. Brace yourself. SwiftPM is just not ready This post risks having a negative vibe, so I want to discuss solutions before problems. The first advice I can give if you want to build consistently across platforms is to avoid SwiftPM, except for Swift Testing. It’s just not fit for the role yet. XCFrameworks only work on Apple, and binary artifact bundles are on their way, but haven’t been released yet. If you need something that works today, get familiar with swiftc and clang, and switch to CMake with ninja. With CMake in place, Swift gains back the freedom and power it deserves. You take full control of what’s going on, and this will help you understand and fix the build issues that you’re bound to encounter. At this point, you may come to realize how SwiftPM could lead you to a complex source hierarchy. The highly granular dependency model encouraged by the manifest is a neat abstraction, and works very well for modular Swift-only systems. But when you have to account for C/C++ targets, modulemaps, DocC, and external non-Swift libraries, it makes you want to jump off the nearest cliff without even a goodbye letter. In that case, making your source hierarchy flat and monolithic will make your life much easier. Two targets: one for Swift files, and one for C/C++ with a single modulemap. Unfortunately, there’s one thing you might want SwiftPM for: the Swift SDKs. For what it’s worth, I use a Swift SDK for my Android builds, and I have no idea if there’s a place for it in a CMake build, but I kind of doubt it. Nevertheless, given that Passepartout –and other consumers– still need Partout as a SwiftPM dependency, I’m okay with keeping both the Package.swift and CMake around for some time. The Swift runtime There are two common ways to compile Swift files: swiftc swift build (Swift Package Manager) The majority of Swift programmers, hardly use or tweak swiftc directly, which is similar to how one would use the clang or gcc C/C++ compilers. Over the years, the Xcode integration and the fast turnaround of SwiftPM execution from the command line made swift build the natural way to compile and link Swift projects, both standalone and inside Xcode. This is all great, until it’s not. The lack of insight into what SwiftPM exactly does may be very problematic when we face a compiler or linker issue. However, pure Swift projects hardly hit major build issues, so don’t be surprised if the average iOS programmer doesn’t know how a Swift executable is generated or what it needs to run on a machine, because Apple devices come with the Swift runtime preinstalled. The language “runtime” is a set of libraries that an executable requires to run on a specific architecture. Let me explain why I find this a serious blocker for Swift adoption. Binary distribution is impractical Let’s pick Linux, for example, which on a typical modern desktop may come in arm64 or x86_64 flavors. Not only does this imply two runtimes to deal with for distributing our binaries, the Swift runtime is also split into dozens of files. If we close an eye on the footprint, this is still annoying, but less of a problem on platforms where the executable model is self-contained (iOS, macOS, Android, …). You could distribute a self-contained folder/installer on Linux, but it always feels off on a system where libraries are typically shared in /usr/lib for everybody. Installing the Swift runtime with a package manager seems like a smoother solution, and saves us from the burden of manually bundling and maintaining the binaries for each architecture. To my knowledge, apt has a libswiftlang package. I don’t know how maintained it is, because the apt package is at 6.0.3 with the latest toolchain being at 6.1.2. Not a 100% healthy sign, but not worrying either. Still, this introduces a dependency, and forces you to either distribute your executable through a package manager, or include manual steps in order to fetch the proper Swift runtime, for the proper platform, and for the proper architecture. In that regard, I don’t like that swift.org doesn’t offer direct links to download runtime-only installers, like Java used to do with the Java Runtime Environment (JRE). A more convenient solution for Linux was introduced in Swift 5.3.1 with static linking in an attempt to match the self-contained approach of Golang, which is the state of the art for native multiplatform applications. Static linking to the stdlib is not as straightforward as running go build, but still. The loneliness of the other platforms All things considered, the friction remains very real. Now, if this already sounds complicated, what if I told you that Linux is the best supported platform after Apple’s? The weight of Windows and Android is on the shoulders of a few kind individuals, whose progress can be followed on the official Swift forums. For example, hard work is being done to bring static linking on the Windows runtime as of Swift 6.2 (unreleased yet), and a few months ago, an Android workgroup was made official. Until then, Android development was mostly pushed by volunteers. I’m all about supporting the maintainers of these exciting initiatives, and that’s why I took this winding path to experiment myself, but if you need something that works right away for building production software in Swift, you’d better assume that you’re on your own. There’s still work to be done to make the experience acceptable for the public domain, and Android builds in particular take a lot of manual steps, or way more than a lot. At the end of the day, it depends on your goals. Personally, I was thrilled when I made Partout connect to a VPN on Android, with Linux syscalls over a Swift codebase that talks to Kotlin via JNI (!). That chill of “could this even work?”, because nobody had done it before. You hardly get that feeling with battle-tested languages, and if you like novelties, Swift is a greenfield that I would encourage you to explore and support. The footprint is huge by default In a former article, I mentioned that Foundation is not part of the standard library, as in it’s not part of the essential runtime (libSwiftCore). In terms of size, the core runtime is not excessively concerning at around 10-15MB total. In fact, I’m way more annoyed by the fact that it’s made of tens of files. Well, add Foundation to the mix, and your runtime spikes to almost 100MB, with ICU contributing to around 40MB of it. And Foundation is hard to avoid. Frankly, that’s insane, but I still want to believe that this is the kind legacy from the Apple era of Swift. Let’s not forget that Foundation is bundled with any Apple OS. My advice? If you’re starting from scratch, and footprint is a concern, never import Foundation, or maybe consider not using Swift at all. Go is more mature for the purpose of standalone outputs, easy to build, ubiquitous. If you want to stick with Swift or port your existing codebase to non-Apple, instead, consider dropping Foundation and reimplementing the parts that you need. With careful analysis, you might conclude that, despite its convenience, you don’t need it to the point of justifying a 100MB executable. Bottom line So, it’s 2025 and you want to make native cross-platform apps or libraries. Hard truth: if you want to make them today, Go is a wiser choice. If you want to use Swift, for now, prefer CMake over SwiftPM. Get your feet wet with the raw tooling to understand how Swift libraries are generated. Don’t be like me, keep a flat source hierarchy from the beginning. In the next article, we’ll go through real examples of how I managed to build consistent outputs with SwiftPM/CMake on macOS, Linux, Windows, and even Android.]]></summary></entry><entry><title type="html">The role of AI in losing care for our products</title><link href="https://davidederosa.com/2025/08/the-role-of-ai-in-losing-care-for-our-products/" rel="alternate" type="text/html" title="The role of AI in losing care for our products" /><published>2025-08-24T00:00:00+02:00</published><updated>2025-08-24T00:00:00+02:00</updated><id>https://davidederosa.com/2025/08/the-role-of-ai-in-losing-care-for-our-products</id><content type="html" xml:base="https://davidederosa.com/2025/08/the-role-of-ai-in-losing-care-for-our-products/"><![CDATA[<p>While I have nothing against “vibe coding” per se, I think it harms software-making in the way too many casual relationships do to the ability to make long-term bonds. In fact, it’s not about coding at all. It’s about building “care” for the product.</p>

<p>If I replace MVP with ONS in the equation, I notice eerie similarities. You build MVPs faster with AI, as you may get sex faster with a dating app, for example. You may even feel more accomplished, like non-programmers do when they “build things without having a clue”.</p>

<p>However, the easier you get ONS, the harder it becomes to commit to an intimate relationship. Similarly, the faster you build MVPs, the harder it gets to create software that lasts, because humans tend to care less for things they didn’t put real effort into.</p>

<p>I’m curious to see how many AI-gen software will survive the first year. LLMs produce write-only code by default, and it takes a decent amount of care to lead them in the right direction towards a well-designed product. Only someone who cares will step in and “take care of that”.</p>

<p>So, I disagree with the focus on bad code quality, that one is a much easier problem to solve. I’m rather worried about how indulging in AI may affect our ability to care about the things we create.</p>]]></content><author><name>Davide De Rosa</name></author><category term="llm" /><category term="ai" /><summary type="html"><![CDATA[While I have nothing against “vibe coding” per se, I think it harms software-making in the way too many casual relationships do to the ability to make long-term bonds. In fact, it’s not about coding at all. It’s about building “care” for the product. If I replace MVP with ONS in the equation, I notice eerie similarities. You build MVPs faster with AI, as you may get sex faster with a dating app, for example. You may even feel more accomplished, like non-programmers do when they “build things without having a clue”. However, the easier you get ONS, the harder it becomes to commit to an intimate relationship. Similarly, the faster you build MVPs, the harder it gets to create software that lasts, because humans tend to care less for things they didn’t put real effort into. I’m curious to see how many AI-gen software will survive the first year. LLMs produce write-only code by default, and it takes a decent amount of care to lead them in the right direction towards a well-designed product. Only someone who cares will step in and “take care of that”. So, I disagree with the focus on bad code quality, that one is a much easier problem to solve. I’m rather worried about how indulging in AI may affect our ability to care about the things we create.]]></summary></entry><entry><title type="html">Cross-platform Swift: C interop (pt. 2)</title><link href="https://davidederosa.com/cross-platform-swift/c-interop-part-two/" rel="alternate" type="text/html" title="Cross-platform Swift: C interop (pt. 2)" /><published>2025-07-10T00:00:00+02:00</published><updated>2025-07-10T00:00:00+02:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-c-interop-part-two</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/c-interop-part-two/"><![CDATA[<p>While writing C in a Swift context makes no difference at all, it takes a few tricks to reduce the friction in Swift code that embeds C routines. You will find tangible examples of what I describe here <a href="https://github.com/keeshux/cross-platform-swift">on my GitHub repository</a>. Let’s go through them together.</p>

<h2 id="the-mystique-of-swift-pointers">The mystique of Swift pointers</h2>

<p>Swift has often changed the way it interacts with C entities, especially around version 3 and 4, IIRC. It has changed so much that upgrading Xcode to a new minor version would likely break some code with obscure error messages. To this day, the ambiguity around Swift pointer types still beats me.</p>

<p>Have you ever noticed how many variants exist?</p>

<ul>
  <li><a href="https://developer.apple.com/documentation/swift/unsafepointer">UnsafePointer</a></li>
  <li><a href="https://developer.apple.com/documentation/swift/unsafebufferpointer">UnsafeBufferPointer</a></li>
  <li><a href="https://developer.apple.com/documentation/swift/unsaferawpointer">UnsafeRawPointer</a></li>
  <li><a href="https://developer.apple.com/documentation/swift/unsaferawbufferpointer">UnsafeRawBufferPointer</a></li>
</ul>

<p>Each of them with its mutable counterpart, totalling EIGHT pointer types. Don’t be offended, Swift, but this is ridiculously complex, and I hope that someday that part of the language will be properly simplified. It’s the single thing, hands down, that makes Swift/C interop unappealing.</p>

<p>Personally, I have a hard time getting them right, and find myself bruteforcing the Swift code until I get it to compile. Don’t even get me started on the <code class="language-plaintext highlighter-rouge">withUnsafeBytes()</code> variants and the horrendously deceptive compiler/LSP errors that they trigger. They are very similar to the ones you stumble upon in complex SwiftUI closures with generics, to get the idea.</p>

<p>Let me show you some lovely examples:</p>

<p><img src="/s/f/cross-platform-swift/c-interop-compile-01.png" alt="" /></p>

<p><img src="/s/f/cross-platform-swift/c-interop-compile-02.png" alt="" /></p>

<p><img src="/s/f/cross-platform-swift/c-interop-compile-03.png" alt="" /></p>

<p><img src="/s/f/cross-platform-swift/c-interop-compile-04.png" alt="" /></p>

<h2 id="unsafe-with-closures">Unsafe <code class="language-plaintext highlighter-rouge">with</code> closures</h2>

<p>When using <code class="language-plaintext highlighter-rouge">withUnsafeBytes()</code>, <code class="language-plaintext highlighter-rouge">withCString()</code>, and other similar closures, make sure to follow these ultimate 3 rules:</p>

<ul>
  <li><strong>Never ever let the closure arguments outlive the closure</strong>.</li>
  <li><strong>Never</strong>.</li>
  <li><strong>Ever</strong>.</li>
</ul>

<p>Thank me later. You’re welcome.</p>

<p>This is another clunky syntax of Swift. Especially if your C function requires multiple variables to be mapped to their “unsafe representation”, you may quickly end up with this beautiful accordion:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="o">...</span>
<span class="k">var</span> <span class="nv">b</span> <span class="o">=</span> <span class="o">...</span>
<span class="k">var</span> <span class="nv">c</span> <span class="o">=</span> <span class="o">...</span>
<span class="n">a</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="n">pa</span> <span class="k">in</span>
    <span class="n">b</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="n">pb</span> <span class="k">in</span>
        <span class="n">c</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="n">pc</span> <span class="k">in</span>
            <span class="nf">my_demanding_c_function</span><span class="p">(</span><span class="n">pa</span><span class="p">,</span> <span class="n">pb</span><span class="p">,</span> <span class="n">pc</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then, disgusted by the indentation overflow, you might look for ways to make the code more linear, and naturally think of returning the pointers that the closures provide:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">pa</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">pb</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">pc</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">withUnsafeBytes</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="nf">my_demanding_c_function</span><span class="p">(</span><span class="n">pa</span><span class="p">,</span> <span class="n">pb</span><span class="p">,</span> <span class="n">pc</span><span class="p">)</span>
</code></pre></div></div>

<p>Much, much cleaner, only to find out that this code will crash, sooner or later, at some point of your life. They call it <em>unpredictable behavior</em>, and I guess it’s for the same reasons why you shouldn’t return a reference to a local variable. So, remember <em>the</em> rule, and kindly accept the nested closures. Or, keep reading.</p>

<h2 id="bridging-to-swift">Bridging to Swift</h2>

<p>Ironically, the above constructs are enough to minimize or even discourage the use of C in Swift. The time I wasted on Swift pointers made me constantly reconsider the balance between Swift and C code %, in that exposing C pointers to Swift was so frustrating that I’d rather write the whole logic in C, and let Swift be a thin wrapper. After all, it’s the best approach because going back and forth from and to Swift/C types is very inconvenient. Why would you ever use a bare <code class="language-plaintext highlighter-rouge">memcpy()</code> or <code class="language-plaintext highlighter-rouge">strlen()</code> in Swift? There are better, native alternatives.</p>

<p>The point of C interop is to perform the low-level logic in C files, and only expose in/out types that are easier to funnel to Swift for use at higher levels of abstraction, especially if you make good use of the <code class="language-plaintext highlighter-rouge">_Nullable</code> and <code class="language-plaintext highlighter-rouge">_Nonnull</code> clang qualifiers.</p>

<p>All in all, Swift bridges C <code class="language-plaintext highlighter-rouge">struct</code> as if it were a Swift value type, and pointers to <code class="language-plaintext highlighter-rouge">struct</code> are naturally mapped to <code class="language-plaintext highlighter-rouge">UnsafePointer&lt;T&gt;</code> or <code class="language-plaintext highlighter-rouge">UnsafeMutablePointer&lt;T&gt;</code> according to their <code class="language-plaintext highlighter-rouge">const</code> modifier. <code class="language-plaintext highlighter-rouge">enum</code> and <code class="language-plaintext highlighter-rouge">union</code> also behave pretty much the same way.</p>

<p>So, what’s the matter? Why would one need more than this? Well, because everything is cool until you hit pointers, strings, and memory management.</p>

<h2 id="opaquepointer">OpaquePointer</h2>

<p>Sure, a C <code class="language-plaintext highlighter-rouge">struct</code> is nicely bridged to Swift, until it has less linear (yet super common) fields like:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">str</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>
    <span class="kt">uint16_t</span> <span class="o">*</span><span class="n">buf</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="kt">char</span> <span class="o">**</span><span class="n">ptr</span><span class="p">);</span>
    <span class="kt">int</span> <span class="o">**</span><span class="n">matrix</span><span class="p">;</span>
<span class="p">}</span> <span class="n">my_cool_struct</span><span class="p">;</span>
</code></pre></div></div>

<p>Good luck with the bridged Swift version of this structure, and good luck to manage its memory layout properly.</p>

<p>But this is when I casually discovered the magic wand, the ultimate structure for C objects in Swift: <a href="https://developer.apple.com/documentation/swift/opaquepointer">OpaquePointer</a>.</p>

<p><img src="/s/f/cross-platform-swift/c-interop-opaque-pointers.jpg" alt="Courtesy of Reddit" /></p>

<p>Opaque pointers are a popular way to attain OOP-like encapsulation in C. You write a <em>forward declaration</em> in a .h header, then the full definition in a .c file. By doing so, the type internals are only exposed to the .c file that needs them to implement its logic. Externally, the pointers are treated as generic I/O handles, and Swift can’t see through them because it can only bridge what it sees in the C headers, i.e. a shallow type name. For the record, Objective-C can do this with <code class="language-plaintext highlighter-rouge">@class MyType</code>.</p>

<h3 id="the-benefits-of-opaquepointer">The benefits of OpaquePointer</h3>

<p>For example, if we have this structure in a C header:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .h</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">tls_channel_options</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">opt</span><span class="p">;</span>
    <span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">ssl_ctx</span><span class="p">;</span>
    <span class="kt">size_t</span> <span class="n">buf_len</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">buf_cipher</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">buf_plain</span><span class="p">;</span>

    <span class="n">SSL</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">ssl</span><span class="p">;</span>
    <span class="n">BIO</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">bio_plain</span><span class="p">;</span>
    <span class="n">BIO</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">bio_cipher_in</span><span class="p">;</span>
    <span class="n">BIO</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">bio_cipher_out</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">is_connected</span><span class="p">;</span>
<span class="p">}</span> <span class="n">tls_channel</span><span class="p">;</span>
</code></pre></div></div>

<p>and return e.g. <code class="language-plaintext highlighter-rouge">tls_channel *</code> from a function, its type will map to <code class="language-plaintext highlighter-rouge">UnsafeMutablePointer&lt;tls_channel&gt;</code> in Swift, and we’ll be able to access its fields with <code class="language-plaintext highlighter-rouge">.pointee</code>. We don’t need or want that level of detail, those are C concerns.</p>

<p>Therefore, we split the definitions across two files. The header with a forward pointer declaration:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .h</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">tls_channel</span> <span class="o">*</span><span class="n">tls_channel_ctx</span><span class="p">;</span>
</code></pre></div></div>

<p>and the .c file, with the full definition, mind the lack of <code class="language-plaintext highlighter-rouge">typedef</code> here:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .c</span>
<span class="k">struct</span> <span class="n">tls_channel</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">tls_channel_options</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">opt</span><span class="p">;</span>
    <span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">_Nonnull</span> <span class="n">ssl_ctx</span><span class="p">;</span>
    <span class="c1">// ...</span>
<span class="p">};</span>
</code></pre></div></div>

<p>At this point, we replace <code class="language-plaintext highlighter-rouge">tls_channel *</code> with the <code class="language-plaintext highlighter-rouge">tls_channel_ctx</code> alias:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// before</span>
<span class="n">tls_channel</span> <span class="o">*</span><span class="nf">tls_create_func</span><span class="p">();</span>

<span class="c1">// after</span>
<span class="n">tls_channel_ctx</span> <span class="nf">tls_create_func</span><span class="p">();</span>
</code></pre></div></div>

<p>and Swift will consider the returned object to be of <code class="language-plaintext highlighter-rouge">OpaquePointer</code> type.</p>

<p>Benefits:</p>

<ul>
  <li>No unsafe Swift pointers involved ever again</li>
  <li>No unsafe <code class="language-plaintext highlighter-rouge">with</code> closures, you pass the object as is to C functions from Swift</li>
  <li>The C layer is the only responsible of the object, and Swift cannot mess with it</li>
</ul>

<p>Granted, it’s not a one-size-fits-it-all, but for complex C objects that need to cross the Swift boundary often, opaque pointers may be a good bet.</p>

<p><img src="/s/f/cross-platform-swift/c-interop-opaque-pointers-example.png" alt="Example in Swift" /></p>

<h2 id="debugging">Debugging</h2>

<p>Talking about the Xcode debugger, it is generally able to debug C code called from Swift without issues. Not that I pushed this to the limit, but so far I’ve only noticed two places where the debugger is unhelpful.</p>

<p>For example, due to their very nature, you don’t get to see what opaque pointers point to in Swift code. However, opaque pointers encapsulate private data, so a logical deduction would be that you’d rather put your breakpoint in C files, and let Swift treat them as a black box.</p>

<p>The other situation is <code class="language-plaintext highlighter-rouge">inline</code> functions, where the Xcode debugger clearly struggles to step in (with reason?).</p>

<h2 id="bottom-line">Bottom line</h2>

<p>Interacting with Swift pointers is painful, but we can make our lives easier with some workarounds. The friction is directly proportional to the surface we expose to Swift, so we need to hide the C internals as much as possible, and minimize the roundtrips across languages. Use opaque pointers when types become complex.</p>

<p>Now that we learned a few tricks about C interop, we can go back to the SwiftPM manifest to manage multiple, alternative implementations. See you in the next article.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><summary type="html"><![CDATA[While writing C in a Swift context makes no difference at all, it takes a few tricks to reduce the friction in Swift code that embeds C routines. You will find tangible examples of what I describe here on my GitHub repository. Let’s go through them together. The mystique of Swift pointers Swift has often changed the way it interacts with C entities, especially around version 3 and 4, IIRC. It has changed so much that upgrading Xcode to a new minor version would likely break some code with obscure error messages. To this day, the ambiguity around Swift pointer types still beats me. Have you ever noticed how many variants exist? UnsafePointer UnsafeBufferPointer UnsafeRawPointer UnsafeRawBufferPointer Each of them with its mutable counterpart, totalling EIGHT pointer types. Don’t be offended, Swift, but this is ridiculously complex, and I hope that someday that part of the language will be properly simplified. It’s the single thing, hands down, that makes Swift/C interop unappealing. Personally, I have a hard time getting them right, and find myself bruteforcing the Swift code until I get it to compile. Don’t even get me started on the withUnsafeBytes() variants and the horrendously deceptive compiler/LSP errors that they trigger. They are very similar to the ones you stumble upon in complex SwiftUI closures with generics, to get the idea. Let me show you some lovely examples: Unsafe with closures When using withUnsafeBytes(), withCString(), and other similar closures, make sure to follow these ultimate 3 rules: Never ever let the closure arguments outlive the closure. Never. Ever. Thank me later. You’re welcome. This is another clunky syntax of Swift. Especially if your C function requires multiple variables to be mapped to their “unsafe representation”, you may quickly end up with this beautiful accordion: var a = ... var b = ... var c = ... a.withUnsafeBytes { pa in b.withUnsafeBytes { pb in c.withUnsafeBytes { pc in my_demanding_c_function(pa, pb, pc) } } } Then, disgusted by the indentation overflow, you might look for ways to make the code more linear, and naturally think of returning the pointers that the closures provide: let pa = a.withUnsafeBytes { $0 } let pb = b.withUnsafeBytes { $0 } let pc = c.withUnsafeBytes { $0 } my_demanding_c_function(pa, pb, pc) Much, much cleaner, only to find out that this code will crash, sooner or later, at some point of your life. They call it unpredictable behavior, and I guess it’s for the same reasons why you shouldn’t return a reference to a local variable. So, remember the rule, and kindly accept the nested closures. Or, keep reading. Bridging to Swift Ironically, the above constructs are enough to minimize or even discourage the use of C in Swift. The time I wasted on Swift pointers made me constantly reconsider the balance between Swift and C code %, in that exposing C pointers to Swift was so frustrating that I’d rather write the whole logic in C, and let Swift be a thin wrapper. After all, it’s the best approach because going back and forth from and to Swift/C types is very inconvenient. Why would you ever use a bare memcpy() or strlen() in Swift? There are better, native alternatives. The point of C interop is to perform the low-level logic in C files, and only expose in/out types that are easier to funnel to Swift for use at higher levels of abstraction, especially if you make good use of the _Nullable and _Nonnull clang qualifiers. All in all, Swift bridges C struct as if it were a Swift value type, and pointers to struct are naturally mapped to UnsafePointer&lt;T&gt; or UnsafeMutablePointer&lt;T&gt; according to their const modifier. enum and union also behave pretty much the same way. So, what’s the matter? Why would one need more than this? Well, because everything is cool until you hit pointers, strings, and memory management. OpaquePointer Sure, a C struct is nicely bridged to Swift, until it has less linear (yet super common) fields like: typedef struct { char str[32]; uint16_t *buf; const void (*)(char **ptr); int **matrix; } my_cool_struct; Good luck with the bridged Swift version of this structure, and good luck to manage its memory layout properly. But this is when I casually discovered the magic wand, the ultimate structure for C objects in Swift: OpaquePointer. Opaque pointers are a popular way to attain OOP-like encapsulation in C. You write a forward declaration in a .h header, then the full definition in a .c file. By doing so, the type internals are only exposed to the .c file that needs them to implement its logic. Externally, the pointers are treated as generic I/O handles, and Swift can’t see through them because it can only bridge what it sees in the C headers, i.e. a shallow type name. For the record, Objective-C can do this with @class MyType. The benefits of OpaquePointer For example, if we have this structure in a C header: // .h typedef struct { const tls_channel_options *_Nonnull opt; SSL_CTX *_Nonnull ssl_ctx; size_t buf_len; uint8_t *_Nonnull buf_cipher; uint8_t *_Nonnull buf_plain; SSL *_Nonnull ssl; BIO *_Nonnull bio_plain; BIO *_Nonnull bio_cipher_in; BIO *_Nonnull bio_cipher_out; bool is_connected; } tls_channel; and return e.g. tls_channel * from a function, its type will map to UnsafeMutablePointer&lt;tls_channel&gt; in Swift, and we’ll be able to access its fields with .pointee. We don’t need or want that level of detail, those are C concerns. Therefore, we split the definitions across two files. The header with a forward pointer declaration: // .h typedef struct tls_channel *tls_channel_ctx; and the .c file, with the full definition, mind the lack of typedef here: // .c struct tls_channel { const tls_channel_options *_Nonnull opt; SSL_CTX *_Nonnull ssl_ctx; // ... }; At this point, we replace tls_channel * with the tls_channel_ctx alias: // before tls_channel *tls_create_func(); // after tls_channel_ctx tls_create_func(); and Swift will consider the returned object to be of OpaquePointer type. Benefits: No unsafe Swift pointers involved ever again No unsafe with closures, you pass the object as is to C functions from Swift The C layer is the only responsible of the object, and Swift cannot mess with it Granted, it’s not a one-size-fits-it-all, but for complex C objects that need to cross the Swift boundary often, opaque pointers may be a good bet. Debugging Talking about the Xcode debugger, it is generally able to debug C code called from Swift without issues. Not that I pushed this to the limit, but so far I’ve only noticed two places where the debugger is unhelpful. For example, due to their very nature, you don’t get to see what opaque pointers point to in Swift code. However, opaque pointers encapsulate private data, so a logical deduction would be that you’d rather put your breakpoint in C files, and let Swift treat them as a black box. The other situation is inline functions, where the Xcode debugger clearly struggles to step in (with reason?). Bottom line Interacting with Swift pointers is painful, but we can make our lives easier with some workarounds. The friction is directly proportional to the surface we expose to Swift, so we need to hide the C internals as much as possible, and minimize the roundtrips across languages. Use opaque pointers when types become complex. Now that we learned a few tricks about C interop, we can go back to the SwiftPM manifest to manage multiple, alternative implementations. See you in the next article.]]></summary></entry><entry><title type="html">Cross-platform Swift: C interop (pt. 1)</title><link href="https://davidederosa.com/cross-platform-swift/c-interop-part-one/" rel="alternate" type="text/html" title="Cross-platform Swift: C interop (pt. 1)" /><published>2025-07-09T00:00:00+02:00</published><updated>2025-07-09T00:00:00+02:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-c-interop-part-one</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/c-interop-part-one/"><![CDATA[<p>This is where the fun begins. <a href="https://github.com/partout-io/partout">Partout</a>, the library I talk about in this series, is a networking library. As such, there are places where code control and performance are paramount. The integration of Swift with the C language for low-level routines is neat, and SwiftPM makes it very easy to mix Swift and C code as if they were the same thing.</p>

<h2 id="the-dangers-of-apple-mix-and-match">The dangers of Apple “Mix and Match”</h2>

<p><img src="/s/f/cross-platform-swift/c-interop-objc-tinky-winky.jpg" alt="ObjC is the Tinky Winky hand" /></p>

<p>I said “mix Swift and C code”, but I should have said “Swift, C, and Objective-C code”. Given that Swift was born off the ribs of Objective-C and the everlasting NeXSTEP library –I know you love that ubiquitous <code class="language-plaintext highlighter-rouge">NS</code> prefix–, it still owes a big legacy to that system. Since the beginning, Apple offered great tools to make <a href="https://developer.apple.com/documentation/swift/importing-objective-c-into-swift">the migration from ObjC to Swift</a> as seamless as possible. In fact, the process has been so smooth that you may have a hard time spotting the Objective-C entities of a Swift codebase.</p>

<p>While this is amazing for Apple-oriented software with plenty of legacy, this is no less than a disgrace when you port Swift elsewhere, because <a href="/cross-platform-swift/#2-objc-no-thanks">Objective-C is not available to Swift</a> out of the Apple ecosystem. At least, not easily <em>at all</em>.</p>

<h2 id="embrace-the-power-of-c">Embrace the power of C</h2>

<p>The mix-and-match transition to Swift may lead your codebase to a place where it’s very difficult to gauge the extent of the Objective-C legacy. Why is this important? Because if your intent is going cross-platform, you’d better port all your Objective-C code to good old C. The alternative is manually linking against <a href="https://www.gnustep.org/">GNUstep</a>, but it’s niche GPL software, and adds unnecessary complexity to keep around a language that is already obsolete.</p>

<p>In my case, the low-level part of my OpenVPN implementation in Partout was written in Objective-C plus OpenSSL, so I decided it was time to <a href="https://github.com/passepartoutvpn/partout/milestone/4?closed=1">patiently write a new C version</a>.</p>

<p>You have no idea how thrilled I am that <strong>this new Swift/C implementation now works on Apple, Windows, Linux, and Android</strong>!</p>

<p>It took me a couple of weeks to have a successful connection over the new code, and not only was it more <em>pleasant</em> (Objective-C is not praised for its beauty), it was also significantly <em>faster</em>. The image below shows a speed test comparing the performance of my C (1) and ObjC (2) implementations of the OpenVPN protocol over the same 5G connection.</p>

<p>Can I finally mention the joy of using C again after years of high-level programming?</p>

<script src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fpassepartoutvpn%2Fpartout%2Fblob%2F66f3ed5c01c8d357a7b625a1067f19f368698124%2FSources%2FCrypto%2FOpenSSL_ObjC%2FCryptoAEAD.m%23L37-L58&amp;style=default&amp;type=code&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showFullPath=on&amp;showCopy=on"></script>

<script src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fpassepartoutvpn%2Fpartout%2Fblob%2F66f3ed5c01c8d357a7b625a1067f19f368698124%2FSources%2FCrypto%2FOpenSSL_C%2Fcrypto_aead.c%23L26-L42&amp;style=default&amp;type=code&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showFullPath=on&amp;showCopy=on"></script>

<p><img src="/s/f/cross-platform-swift/c-interop-speed-test.png" alt="OpenVPN over C versus ObjC" /></p>

<h2 id="let-countless-options-unfold">Let countless options unfold</h2>

<p>As you dig into C interop, you realize how many things you can do with Swift in both an abstract and performant fashion. WWDC 2024 even introduced the <a href="https://www.swift.org/get-started/embedded/">Embedded Swift</a> mode to <em>really</em> go thin and low-level, and C is the natural choice when it comes to communicating with an operating system API, or non-Swift libraries.</p>

<p>At a later stage, we’ll have to face the complications of distributing the Swift runtime, but having C at disposal may dramatically reduce the impact of third-party dependencies, including those Swift itself relies on. Let me anticipate a few examples:</p>

<ul>
  <li>The burden of Foundation, or even Concurrency, may be avoided with a tailored C API for our specific domain.</li>
  <li>The WireGuard backend, that Partout currently integrates via <a href="https://github.com/WireGuard/wireguard-go">wireguard-go</a>, can communicate with the Linux/Windows kernel module instead.</li>
  <li>If the footprint is important, the OpenSSL dependency may be replaced with OS APIs, or used in SwiftPM as a shared library. This is common on Linux.</li>
</ul>

<script src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fpassepartoutvpn%2Fpartout%2Fblob%2F66f3ed5c01c8d357a7b625a1067f19f368698124%2FPackage.swift%23L334-L346&amp;style=default&amp;type=code&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showFullPath=on&amp;showCopy=on"></script>

<h2 id="bottom-line">Bottom line</h2>

<p>C is a first-class citizen in the Swift ecosystem, and it’s the door to literally everything about a device. Keep enjoying the convenience of the modern Swift abstractions, but don’t fear the power of C when necessary, because C code compiles everywhere with little to no modifications. Some stuff is incredibly easier in C than Swift.</p>

<p>In the next article, I’ll show you how to survive the deceptive complexity of Swift/C bridging.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><summary type="html"><![CDATA[This is where the fun begins. Partout, the library I talk about in this series, is a networking library. As such, there are places where code control and performance are paramount. The integration of Swift with the C language for low-level routines is neat, and SwiftPM makes it very easy to mix Swift and C code as if they were the same thing. The dangers of Apple “Mix and Match” I said “mix Swift and C code”, but I should have said “Swift, C, and Objective-C code”. Given that Swift was born off the ribs of Objective-C and the everlasting NeXSTEP library –I know you love that ubiquitous NS prefix–, it still owes a big legacy to that system. Since the beginning, Apple offered great tools to make the migration from ObjC to Swift as seamless as possible. In fact, the process has been so smooth that you may have a hard time spotting the Objective-C entities of a Swift codebase. While this is amazing for Apple-oriented software with plenty of legacy, this is no less than a disgrace when you port Swift elsewhere, because Objective-C is not available to Swift out of the Apple ecosystem. At least, not easily at all. Embrace the power of C The mix-and-match transition to Swift may lead your codebase to a place where it’s very difficult to gauge the extent of the Objective-C legacy. Why is this important? Because if your intent is going cross-platform, you’d better port all your Objective-C code to good old C. The alternative is manually linking against GNUstep, but it’s niche GPL software, and adds unnecessary complexity to keep around a language that is already obsolete. In my case, the low-level part of my OpenVPN implementation in Partout was written in Objective-C plus OpenSSL, so I decided it was time to patiently write a new C version. You have no idea how thrilled I am that this new Swift/C implementation now works on Apple, Windows, Linux, and Android! It took me a couple of weeks to have a successful connection over the new code, and not only was it more pleasant (Objective-C is not praised for its beauty), it was also significantly faster. The image below shows a speed test comparing the performance of my C (1) and ObjC (2) implementations of the OpenVPN protocol over the same 5G connection. Can I finally mention the joy of using C again after years of high-level programming? Let countless options unfold As you dig into C interop, you realize how many things you can do with Swift in both an abstract and performant fashion. WWDC 2024 even introduced the Embedded Swift mode to really go thin and low-level, and C is the natural choice when it comes to communicating with an operating system API, or non-Swift libraries. At a later stage, we’ll have to face the complications of distributing the Swift runtime, but having C at disposal may dramatically reduce the impact of third-party dependencies, including those Swift itself relies on. Let me anticipate a few examples: The burden of Foundation, or even Concurrency, may be avoided with a tailored C API for our specific domain. The WireGuard backend, that Partout currently integrates via wireguard-go, can communicate with the Linux/Windows kernel module instead. If the footprint is important, the OpenSSL dependency may be replaced with OS APIs, or used in SwiftPM as a shared library. This is common on Linux. Bottom line C is a first-class citizen in the Swift ecosystem, and it’s the door to literally everything about a device. Keep enjoying the convenience of the modern Swift abstractions, but don’t fear the power of C when necessary, because C code compiles everywhere with little to no modifications. Some stuff is incredibly easier in C than Swift. In the next article, I’ll show you how to survive the deceptive complexity of Swift/C bridging.]]></summary></entry><entry><title type="html">Cross-platform Swift: Platform specifics</title><link href="https://davidederosa.com/cross-platform-swift/platform-specifics/" rel="alternate" type="text/html" title="Cross-platform Swift: Platform specifics" /><published>2025-06-30T00:00:00+02:00</published><updated>2025-06-30T00:00:00+02:00</updated><id>https://davidederosa.com/cross-platform-swift/cross-platform-swift-platform-specifics</id><content type="html" xml:base="https://davidederosa.com/cross-platform-swift/platform-specifics/"><![CDATA[<p>Very few developers treat the SwiftPM manifest, i.e. the <code class="language-plaintext highlighter-rouge">Package.swift</code> file, as what its extension suggests. If you are used to enumerate static products and targets, remember that the SwiftPM manifest is a fully legit Swift program, and as such it allows for plenty of control.</p>

<h2 id="conditionals-in-packageswift">Conditionals in Package.swift</h2>

<p>In a library whose aim is to target multiple platforms, and try different paths along the way, a flexible manifest is of great help. Partout uses generic Swift routines overall, but in some areas it needs to differentiate how things are done based on the platform it’s running on.</p>

<p>Think of:</p>

<ul>
  <li>DNS resolution (CFHost vs POSIX)</li>
  <li>Pseudo-random number generation (SecRandom vs getrandom)</li>
  <li>Access to the filesystem (FileManager vs FILE *)</li>
</ul>

<p>In some cases, even provide additional behavior that is not available on other platforms. If you started our Swift library on Apple platforms, like it’s often the case, you may have used plenty of frameworks that are tighly bound to Apple. Examples:</p>

<ul>
  <li>UserDefaults</li>
  <li>The keychain</li>
  <li>iCloud</li>
  <li>Camera</li>
  <li>…</li>
</ul>

<h2 id="building-on-non-apple">Building on non-Apple</h2>

<p>While it’s true that the <code class="language-plaintext highlighter-rouge">Foundation</code> framework may provide some platform-agnostic API (e.g. <code class="language-plaintext highlighter-rouge">UserDefaults</code>), it’s better to take full control of what’s being distributed based on our choices. If you care to split platform-specific code into separate targets, you can leverage the <code class="language-plaintext highlighter-rouge">condition</code> parameter of a dependency to fine-tune per-platform dependencies:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">target</span><span class="p">(</span>
    <span class="nv">name</span><span class="p">:</span> <span class="s">"SomeMultiCode"</span><span class="p">,</span>
    <span class="nv">dependencies</span><span class="p">:</span> <span class="p">[</span>
        <span class="o">.</span><span class="nf">target</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"SomeiOSDep"</span><span class="p">,</span> <span class="nv">condition</span><span class="p">:</span> <span class="o">.</span><span class="nf">when</span><span class="p">(</span><span class="nv">platforms</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="n">iOS</span><span class="p">])),</span>
        <span class="o">.</span><span class="nf">target</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"SomeWindowsDep"</span><span class="p">,</span> <span class="nv">condition</span><span class="p">:</span> <span class="o">.</span><span class="nf">when</span><span class="p">(</span><span class="nv">platforms</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="n">windows</span><span class="p">])),</span>
        <span class="c1">// ...</span>
        <span class="c1">// same with .product</span>
    <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Invoking <code class="language-plaintext highlighter-rouge">swift build --target &lt;some&gt;</code> will comply with the platform conditionals, whereas this (still) seems to be a problem for tests. That’s because SwiftPM attempts to build all targets regardless of the conditionals, then compose the filtered targets to assemble the final products, whereas it shouldn’t build some targets in the first place.</p>

<p>A mitigation for this problem is to wrap conditional code in a <code class="language-plaintext highlighter-rouge">canImport</code> condition:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// make sure that the import is available</span>
<span class="cp">#if canImport(Security)</span>
<span class="kd">import</span> <span class="kt">Security</span>

<span class="c1">// do stuff with the Apple Security framework</span>
<span class="cp">#endif</span>
</code></pre></div></div>

<p>This is acceptable inside the library, but how should consumers deal with all these inconvenient differences?</p>

<h2 id="dependency-factory">Dependency factory</h2>

<p>A simple solution to avoid conditionals in consumer apps is the use of a <em>factory</em> to instantiate the right dependencies for our environment. The factory would internally pick the suitable implementation for the current platform, lifting the burden of the choice from the user of the library.</p>

<p>Imagine that we want to provide a common interface for the persistent storage of the library. A natural choice on Apple would be Core Data, and maybe Realm or plain SQLite on Windows and Linux. Furthermore, we may want to support CloudKit synchronization on Apple devices, and omit that feature elsewhere.</p>

<p>In Package.swift:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">target</span><span class="p">(</span>
    <span class="nv">name</span><span class="p">:</span> <span class="s">"MyPersistence"</span>
<span class="p">),</span>
<span class="o">.</span><span class="nf">target</span><span class="p">(</span>
    <span class="nv">name</span><span class="p">:</span> <span class="s">"MyApplePersistence"</span><span class="p">,</span>
    <span class="nv">dependencies</span><span class="p">:</span> <span class="p">[</span><span class="s">"MyPersistence"</span><span class="p">]</span>
<span class="p">),</span>
<span class="o">.</span><span class="nf">target</span><span class="p">(</span>
    <span class="nv">name</span><span class="p">:</span> <span class="s">"MyOtherPersistence"</span><span class="p">,</span>
    <span class="nv">dependencies</span><span class="p">:</span> <span class="p">[</span><span class="s">"MyPersistence"</span><span class="p">]</span>
<span class="p">),</span>
<span class="o">.</span><span class="nf">target</span><span class="p">(</span>
    <span class="nv">name</span><span class="p">:</span> <span class="s">"MyLibrary"</span><span class="p">,</span>
    <span class="nv">dependencies</span><span class="p">:</span> <span class="p">[</span>
        <span class="o">.</span><span class="nf">target</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"MyApplePersistence"</span><span class="p">,</span> <span class="nv">condition</span><span class="p">:</span> <span class="o">.</span><span class="nf">when</span><span class="p">(</span><span class="nv">platforms</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="n">iOS</span><span class="p">,</span> <span class="o">.</span><span class="n">macOS</span><span class="p">,</span> <span class="o">.</span><span class="n">tvOS</span><span class="p">,</span> <span class="o">.</span><span class="n">watchOS</span><span class="p">])),</span>
        <span class="o">.</span><span class="nf">target</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"MyOtherPersistence"</span><span class="p">,</span> <span class="nv">condition</span><span class="p">:</span> <span class="o">.</span><span class="nf">when</span><span class="p">(</span><span class="nv">platforms</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="n">windows</span><span class="p">,</span> <span class="o">.</span><span class="n">linux</span><span class="p">,</span> <span class="o">.</span><span class="n">android</span><span class="p">])),</span>
    <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p>In <code class="language-plaintext highlighter-rouge">MyPersistence</code> we declare the generic persistence API:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">Persistence</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">supportsSynchronization</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

    <span class="kd">func</span> <span class="n">save</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">object</span><span class="p">:</span> <span class="kt">T</span><span class="p">)</span> <span class="k">throws</span>
<span class="p">}</span>
</code></pre></div></div>

<p>that we then implement in <code class="language-plaintext highlighter-rouge">CoreDataPersistence</code> with Core Data, in the <code class="language-plaintext highlighter-rouge">MyApplePersistence</code> target:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">CoreData</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="kt">CoreDataPersistence</span><span class="p">:</span> <span class="kt">Persistence</span> <span class="p">{</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">supportsSynchronization</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="kc">true</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="n">save</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">object</span><span class="p">:</span> <span class="kt">T</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>and <code class="language-plaintext highlighter-rouge">RealmPersistence</code> with Realm, in the <code class="language-plaintext highlighter-rouge">MyOtherPersistence</code> target:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">Realm</span>

<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="kt">RealmPersistence</span><span class="p">:</span> <span class="kt">Persistence</span> <span class="p">{</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">supportsSynchronization</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="kc">false</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="n">save</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">object</span><span class="p">:</span> <span class="kt">T</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the <code class="language-plaintext highlighter-rouge">MyLibrary</code> umbrella target:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="kt">Factories</span> <span class="p">{</span>
<span class="p">}</span>

<span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">PersistenceFactory</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">newPersistence</span><span class="p">(</span><span class="n">at</span> <span class="nv">path</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Persistence</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">Factories</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="k">let</span> <span class="nv">persistence</span> <span class="o">=</span> <span class="kt">PersistenceFactoryImpl</span><span class="p">()</span>
<span class="p">}</span>

<span class="cp">#if canImport(MyApplePersistence)</span>

<span class="c1">// uses Core Data</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kd">class</span> <span class="kt">PersistenceFactoryImpl</span><span class="p">:</span> <span class="kt">PersistenceFactory</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">newPersistence</span><span class="p">(</span><span class="n">at</span> <span class="nv">path</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Persistence</span> <span class="p">{</span>
        <span class="k">try</span> <span class="kt">CoreDataPersistence</span><span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="n">path</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cp">#elseif canImport(MyOtherPersistence)</span>

<span class="c1">// uses Realm</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kd">class</span> <span class="kt">PersistenceFactoryImpl</span><span class="p">:</span> <span class="kt">PersistenceFactory</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">newPersistence</span><span class="p">(</span><span class="n">at</span> <span class="nv">path</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Persistence</span> <span class="p">{</span>
        <span class="k">try</span> <span class="kt">RealmPersistence</span><span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="n">path</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cp">#else</span>

<span class="c1">// unsupported platform</span>

<span class="cp">#endif</span>
</code></pre></div></div>

<p>Finally, in the consumer app we would do this regardless of the operating system:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">someFunction</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">path</span> <span class="o">=</span> <span class="s">"SomeFile.db"</span>
    <span class="k">let</span> <span class="nv">persistence</span> <span class="o">=</span> <span class="kt">Factories</span><span class="o">.</span><span class="n">persistence</span><span class="o">.</span><span class="nf">newPersistence</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">path</span><span class="p">)</span>
    <span class="c1">// ...</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"Supports synchronization: </span><span class="se">\(</span><span class="n">persistence</span><span class="o">.</span><span class="n">supportsSynchronization</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
        <span class="k">try</span> <span class="n">persistence</span><span class="o">.</span><span class="nf">save</span><span class="p">(</span><span class="s">"SomeString"</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The library would just take care of the different implementations.</p>

<h2 id="bottom-line">Bottom line</h2>

<p>It’s easy to overcome the platform differences with SwiftPM conditionals and simple design patterns. If you need even more customizations, don’t be afraid to tweak the manifest further because it’s good old Swift, not static YAML. Even just an <code class="language-plaintext highlighter-rouge">if</code>, or reading environment variables with <a href="https://developer.apple.com/documentation/foundation/processinfo"><code class="language-plaintext highlighter-rouge">ProcessInfo</code></a>, may do wonders and resolve convoluted dynamic package layouts. Yes, in <code class="language-plaintext highlighter-rouge">FOOBAR="value" swift build</code>, the <code class="language-plaintext highlighter-rouge">FOOBAR</code> variable is reachable from your manifest!</p>

<p>In the next article, we’ll start the long but inevitable journey of Swift interoperability with C and other languages.</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="swift" /><category term="c" /><summary type="html"><![CDATA[Very few developers treat the SwiftPM manifest, i.e. the Package.swift file, as what its extension suggests. If you are used to enumerate static products and targets, remember that the SwiftPM manifest is a fully legit Swift program, and as such it allows for plenty of control. Conditionals in Package.swift In a library whose aim is to target multiple platforms, and try different paths along the way, a flexible manifest is of great help. Partout uses generic Swift routines overall, but in some areas it needs to differentiate how things are done based on the platform it’s running on. Think of: DNS resolution (CFHost vs POSIX) Pseudo-random number generation (SecRandom vs getrandom) Access to the filesystem (FileManager vs FILE *) In some cases, even provide additional behavior that is not available on other platforms. If you started our Swift library on Apple platforms, like it’s often the case, you may have used plenty of frameworks that are tighly bound to Apple. Examples: UserDefaults The keychain iCloud Camera … Building on non-Apple While it’s true that the Foundation framework may provide some platform-agnostic API (e.g. UserDefaults), it’s better to take full control of what’s being distributed based on our choices. If you care to split platform-specific code into separate targets, you can leverage the condition parameter of a dependency to fine-tune per-platform dependencies: .target( name: "SomeMultiCode", dependencies: [ .target(name: "SomeiOSDep", condition: .when(platforms: [.iOS])), .target(name: "SomeWindowsDep", condition: .when(platforms: [.windows])), // ... // same with .product ] ) Invoking swift build --target &lt;some&gt; will comply with the platform conditionals, whereas this (still) seems to be a problem for tests. That’s because SwiftPM attempts to build all targets regardless of the conditionals, then compose the filtered targets to assemble the final products, whereas it shouldn’t build some targets in the first place. A mitigation for this problem is to wrap conditional code in a canImport condition: // make sure that the import is available #if canImport(Security) import Security // do stuff with the Apple Security framework #endif This is acceptable inside the library, but how should consumers deal with all these inconvenient differences? Dependency factory A simple solution to avoid conditionals in consumer apps is the use of a factory to instantiate the right dependencies for our environment. The factory would internally pick the suitable implementation for the current platform, lifting the burden of the choice from the user of the library. Imagine that we want to provide a common interface for the persistent storage of the library. A natural choice on Apple would be Core Data, and maybe Realm or plain SQLite on Windows and Linux. Furthermore, we may want to support CloudKit synchronization on Apple devices, and omit that feature elsewhere. In Package.swift: .target( name: "MyPersistence" ), .target( name: "MyApplePersistence", dependencies: ["MyPersistence"] ), .target( name: "MyOtherPersistence", dependencies: ["MyPersistence"] ), .target( name: "MyLibrary", dependencies: [ .target(name: "MyApplePersistence", condition: .when(platforms: [.iOS, .macOS, .tvOS, .watchOS])), .target(name: "MyOtherPersistence", condition: .when(platforms: [.windows, .linux, .android])), ] ) In MyPersistence we declare the generic persistence API: public protocol Persistence { var supportsSynchronization: Bool { get } func save&lt;T&gt;(_ object: T) throws } that we then implement in CoreDataPersistence with Core Data, in the MyApplePersistence target: import CoreData public final class CoreDataPersistence: Persistence { init(path: String) { // ... } var supportsSynchronization: Bool { true } func save&lt;T&gt;(_ object: T) throws { // ... } } and RealmPersistence with Realm, in the MyOtherPersistence target: import Realm public final class RealmPersistence: Persistence { init(path: String) { // ... } var supportsSynchronization: Bool { false } func save&lt;T&gt;(_ object: T) throws { // ... } } In the MyLibrary umbrella target: public enum Factories { } public protocol PersistenceFactory { func newPersistence(at path: String) throws -&gt; Persistence } extension Factories { public static let persistence = PersistenceFactoryImpl() } #if canImport(MyApplePersistence) // uses Core Data private final class PersistenceFactoryImpl: PersistenceFactory { func newPersistence(at path: String) throws -&gt; Persistence { try CoreDataPersistence(path: path) } } #elseif canImport(MyOtherPersistence) // uses Realm private final class PersistenceFactoryImpl: PersistenceFactory { func newPersistence(at path: String) throws -&gt; Persistence { try RealmPersistence(path: path) } } #else // unsupported platform #endif Finally, in the consumer app we would do this regardless of the operating system: func someFunction() { let path = "SomeFile.db" let persistence = Factories.persistence.newPersistence(at: path) // ... do { print("Supports synchronization: \(persistence.supportsSynchronization)") try persistence.save("SomeString") } catch { // ... } } The library would just take care of the different implementations. Bottom line It’s easy to overcome the platform differences with SwiftPM conditionals and simple design patterns. If you need even more customizations, don’t be afraid to tweak the manifest further because it’s good old Swift, not static YAML. Even just an if, or reading environment variables with ProcessInfo, may do wonders and resolve convoluted dynamic package layouts. Yes, in FOOBAR="value" swift build, the FOOBAR variable is reachable from your manifest! In the next article, we’ll start the long but inevitable journey of Swift interoperability with C and other languages.]]></summary></entry><entry><title type="html">A true case for Dependency Injection</title><link href="https://davidederosa.com/2025/05/a-true-case-for-dependency-injection/" rel="alternate" type="text/html" title="A true case for Dependency Injection" /><published>2025-05-24T00:00:00+02:00</published><updated>2025-05-24T00:00:00+02:00</updated><id>https://davidederosa.com/2025/05/a-true-case-for-dependency-injection</id><content type="html" xml:base="https://davidederosa.com/2025/05/a-true-case-for-dependency-injection/"><![CDATA[<p>When studying the foundations of a programming language, for example, during a career in Computer Science, very rarely are <em>design patterns</em> and the concept of <em>software architecture</em> presented in a way that can be transferred to real-world scenarios. Add to that, you may never use most of the well-known patterns, and it’s okay because you don’t want to be a hammer looking for a nail. You shouldn’t force a design pattern for the sake of using it.</p>

<p><a href="#how-di-helps-me-scale-passepartout">Below</a>, I’ll show you how a popular design pattern helped me rework Passepartout for Mac to distribute it <a href="https://github.com/passepartoutvpn/passepartout/issues/231">outside of the App Store</a>, without messing too much with the existing and stable codebase.</p>

<h3 id="experience-produces-patterns">Experience produces patterns</h3>

<p>Patterns are simply an outcome of your programming experience, in that you recognize a common problem for which a common solution also exists. Yet, the amount of buzzwords in the online communities around what some erroneously call “architectures” is ridiculous –the MVVM acronym being the one I stand the least–, to the point that the poor newcomers may feel intimidated. The risk of being ostracized for not using “the right name for the thing” is real, even if the same thing could be named differently the moment you visit a different community.</p>

<p>But hey, listen, the reality is brighter: compilers don’t give a damn about the name you give to your grand architectures. The only names that matter, at the end of the day, are those coming from the syntax of the programming language you use.</p>

<h3 id="popular-design-patterns">Popular design patterns</h3>

<p>Nevertheless, some design patterns are so popular that their names are <em>de facto</em> universal:</p>

<ul>
  <li><a href="https://refactoring.guru/design-patterns/builder">Builder</a></li>
  <li><a href="https://refactoring.guru/design-patterns/command">Command</a></li>
  <li><a href="https://refactoring.guru/design-patterns/factory-method">Factory</a></li>
  <li><a href="https://refactoring.guru/design-patterns/singleton">Singleton</a></li>
  <li><a href="https://refactoring.guru/design-patterns/strategy">Strategy</a></li>
  <li>…</li>
</ul>

<p>You’re bound to cross them at least once in your lifetime. If you like practice more than theory, you definitely used any of the above without the need to give them a name. These patterns are undoubtly useful, they resolve basic architectural problems and, contrary to some beliefs, by no means are they tied to OOP.</p>

<p>Then there is <em>Dependency Injection</em> (DI). I hate giving this one a name, but there it is. More than a design pattern, DI is an <em>approach</em> to making software. I call it that way to avoid the terrible mistake of thinking that there is only one way to do it. I mean, if you’re a Java programmer, you don’t need the <a href="https://spring.io/projects/spring-framework">Spring Framework</a> to do DI.</p>

<p>Here’s how I like to phrase it: <strong>DI is the act of decoupling your software from the underlying implementations of its dependencies.</strong></p>

<h3 id="how-di-helps-me-scale-passepartout">How DI helps me scale Passepartout</h3>

<p>Weeks ago, I decided to make Passepartout for Mac available <a href="https://github.com/passepartoutvpn/passepartout/issues/231">outside of the App Store</a>. Making such a VPN app standalone is no trivial task because the way it operates is radically different: the UI is still a frontend to the VPN backend, but the app and the VPN processes execute as different users, and they do not easily communicate. Specifically, the VPN is deployed as <a href="apple-sysex">System Extension</a>, and therefore runs as <code class="language-plaintext highlighter-rouge">root</code>.</p>

<p>By leaving the App Store, I was about to lose a big chunk of features:</p>

<ul>
  <li>App Groups: required for app/VPN IPC (inter-process communication), data sharing, and logs</li>
  <li>Shared keychain: used to persist the VPN profiles</li>
  <li>In-app purchases</li>
  <li>iCloud</li>
</ul>

<p>In-app purchases and iCloud could be postponed by stripping paid features, but the core functionality of Passepartout relied on both App Groups and the shared keychain. I hit a tough blocker.</p>

<p>Here’s where DI comes to the rescue: <strong>you don’t change the business logic of your software</strong>, you rather change <em>how</em> that logic is implemented deep down in the leaves of the dependency tree. I still wanted to “share data between the app and the VPN process” (business logic), but I needed to change <em>how</em> that data was shared (dependency). Remember, this isolation is only possible if the two concerns are sharply separated, but this is the core principle behind DI.</p>

<p>The core library of Passepartout, <em>Partout,</em> is 100% pure Swift for this reason: the logic of the library is always the same, with the “low-level” implementations abstracted as <code class="language-plaintext highlighter-rouge">protocol</code>s, and for which the app is free to provide (inject) <em>truly</em> different implementations. In my case, it turned out that I could just craft new implementations for the standalone Mac app, whereas anything else would stay untouched.</p>

<h3 id="swap-out-the-broken-pieces">Swap out the broken pieces</h3>

<p>The process is fairly simple:</p>

<ul>
  <li>For each dependency, identify the “broken” implementation of a <code class="language-plaintext highlighter-rouge">protocol</code></li>
  <li>Write a new implementation, compatible with the target</li>
  <li>Unit test behavior as per the <code class="language-plaintext highlighter-rouge">protocol</code> pre/post conditions</li>
  <li>Swap the unsupported implementation with the new one</li>
</ul>

<p>After these steps, passing the tests would imply that the app will work like before, but regardless of being downloaded from the App Store or not. I can’t tell how thrilling it is when you see the magic of programming in action. This is a real-world situation where good practices and constant refactoring pay off.</p>

<p>The new Mac app eventually works around the missing functionalities by dynamically replacing (<code class="language-plaintext highlighter-rouge">supportsAppGroups</code> is false in this case):</p>

<ul>
  <li><a href="https://github.com/passepartoutvpn/partout/blob/master/Sources/Platforms/Apple/UserDefaultsEnvironment.swift"><code class="language-plaintext highlighter-rouge">UserDefaultsEnvironment</code></a> with <a href="https://github.com/passepartoutvpn/partout/blob/master/Sources/Platforms/AppleNE/App/NETunnelEnvironment.swift"><code class="language-plaintext highlighter-rouge">NETunnelEnvironment</code></a>, which does IPC by sending messages to the System Extension, rather than sharing data through the App Group <code class="language-plaintext highlighter-rouge">UserDefaults</code>. They both implement the <code class="language-plaintext highlighter-rouge">TunnelEnvironmentReader</code> protocol, meant for reading VPN data from the app.</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">TunnelEnvironmentReader</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="n">environmentValue</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">forKey</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">TunnelEnvironmentKey</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">?</span> <span class="k">where</span> <span class="kt">T</span><span class="p">:</span> <span class="kt">Decodable</span>
<span class="p">}</span>
</code></pre></div></div>

<script src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fpassepartoutvpn%2Fpassepartout%2Fblob%2F7ffe37ea0d9e280b7cafbeff1332d431173be2d4%2FPassepartout%2FShared%2FDependencies%252BPartout.swift%23L59-L76&amp;style=default&amp;type=code&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showFullPath=on&amp;showCopy=on"></script>

<ul>
  <li><a href="https://github.com/passepartoutvpn/partout/blob/master/Sources/Platforms/AppleNE/Serialization/KeychainNEProtocolCoder.swift"><code class="language-plaintext highlighter-rouge">KeychainNEProtocolCoder</code></a> with <a href="https://github.com/passepartoutvpn/partout/blob/master/Sources/Platforms/AppleNE/Serialization/ProviderNEProtocolCoder.swift"><code class="language-plaintext highlighter-rouge">ProviderNEProtocolCoder</code></a>, which installs the VPN profiles without the keychain, using the Network Extension map in <code class="language-plaintext highlighter-rouge">providerConfiguration</code>. They both implement the <code class="language-plaintext highlighter-rouge">NEProtocolCoder</code> protocol, meant for VPN profiles serialization.</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">NEProtocolCoder</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">protocolConfiguration</span><span class="p">(</span><span class="n">from</span> <span class="nv">profile</span><span class="p">:</span> <span class="kt">Profile</span><span class="p">,</span> <span class="nv">title</span><span class="p">:</span> <span class="p">(</span><span class="kt">Profile</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">NETunnelProviderProtocol</span>

    <span class="kd">func</span> <span class="nf">profile</span><span class="p">(</span><span class="n">from</span> <span class="nv">protocolConfiguration</span><span class="p">:</span> <span class="kt">NETunnelProviderProtocol</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Profile</span>
<span class="p">}</span>
</code></pre></div></div>

<script src="https://emgithub.com/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fpassepartoutvpn%2Fpassepartout%2Fblob%2F7ffe37ea0d9e280b7cafbeff1332d431173be2d4%2FPassepartout%2FShared%2FDependencies%252BPartout.swift%23L40-L57&amp;style=default&amp;type=code&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showFullPath=on&amp;showCopy=on"></script>

<h3 id="do-you-really-need-di">Do you really need DI?</h3>

<p>You’d better follow the DI approach early in your software development, but not immediately, because you may never need to convert a code module into a pluggable dependency. If you foresee that your software will scale enough to justify such an abstraction, then start pulling out third parties from your concrete classes.</p>

<p>By the way, in many corporate projects people use DI because “everybody does”, rather than understanding the real benefits and tradeoffs. The overhead of maintaining abstract layers, or even third parties, for entities that will <em>always</em> have one implementation is sadly laughable. Please, do not avoid concrete classes just because you read that on some book. As I said in the beginning, you shouldn’t use a solution for a problem you don’t have, and most projects don’t need bulky external third parties to deal with their trivial dependencies. If you wasted hours to track down the order and lifecycle of the objects that an app creates on launch, you know what I’m talking about.</p>

<p>Sometimes, it really seems that some modern programmers don’t know how to initialize objects in the first place. In that case, rather than a dependency injection engine –whatever that means–, why not go back to the basics?</p>]]></content><author><name>Davide De Rosa</name></author><category term="development" /><category term="dependency injection" /><summary type="html"><![CDATA[When studying the foundations of a programming language, for example, during a career in Computer Science, very rarely are design patterns and the concept of software architecture presented in a way that can be transferred to real-world scenarios. Add to that, you may never use most of the well-known patterns, and it’s okay because you don’t want to be a hammer looking for a nail. You shouldn’t force a design pattern for the sake of using it. Below, I’ll show you how a popular design pattern helped me rework Passepartout for Mac to distribute it outside of the App Store, without messing too much with the existing and stable codebase. Experience produces patterns Patterns are simply an outcome of your programming experience, in that you recognize a common problem for which a common solution also exists. Yet, the amount of buzzwords in the online communities around what some erroneously call “architectures” is ridiculous –the MVVM acronym being the one I stand the least–, to the point that the poor newcomers may feel intimidated. The risk of being ostracized for not using “the right name for the thing” is real, even if the same thing could be named differently the moment you visit a different community. But hey, listen, the reality is brighter: compilers don’t give a damn about the name you give to your grand architectures. The only names that matter, at the end of the day, are those coming from the syntax of the programming language you use. Popular design patterns Nevertheless, some design patterns are so popular that their names are de facto universal: Builder Command Factory Singleton Strategy … You’re bound to cross them at least once in your lifetime. If you like practice more than theory, you definitely used any of the above without the need to give them a name. These patterns are undoubtly useful, they resolve basic architectural problems and, contrary to some beliefs, by no means are they tied to OOP. Then there is Dependency Injection (DI). I hate giving this one a name, but there it is. More than a design pattern, DI is an approach to making software. I call it that way to avoid the terrible mistake of thinking that there is only one way to do it. I mean, if you’re a Java programmer, you don’t need the Spring Framework to do DI. Here’s how I like to phrase it: DI is the act of decoupling your software from the underlying implementations of its dependencies. How DI helps me scale Passepartout Weeks ago, I decided to make Passepartout for Mac available outside of the App Store. Making such a VPN app standalone is no trivial task because the way it operates is radically different: the UI is still a frontend to the VPN backend, but the app and the VPN processes execute as different users, and they do not easily communicate. Specifically, the VPN is deployed as System Extension, and therefore runs as root. By leaving the App Store, I was about to lose a big chunk of features: App Groups: required for app/VPN IPC (inter-process communication), data sharing, and logs Shared keychain: used to persist the VPN profiles In-app purchases iCloud In-app purchases and iCloud could be postponed by stripping paid features, but the core functionality of Passepartout relied on both App Groups and the shared keychain. I hit a tough blocker. Here’s where DI comes to the rescue: you don’t change the business logic of your software, you rather change how that logic is implemented deep down in the leaves of the dependency tree. I still wanted to “share data between the app and the VPN process” (business logic), but I needed to change how that data was shared (dependency). Remember, this isolation is only possible if the two concerns are sharply separated, but this is the core principle behind DI. The core library of Passepartout, Partout, is 100% pure Swift for this reason: the logic of the library is always the same, with the “low-level” implementations abstracted as protocols, and for which the app is free to provide (inject) truly different implementations. In my case, it turned out that I could just craft new implementations for the standalone Mac app, whereas anything else would stay untouched. Swap out the broken pieces The process is fairly simple: For each dependency, identify the “broken” implementation of a protocol Write a new implementation, compatible with the target Unit test behavior as per the protocol pre/post conditions Swap the unsupported implementation with the new one After these steps, passing the tests would imply that the app will work like before, but regardless of being downloaded from the App Store or not. I can’t tell how thrilling it is when you see the magic of programming in action. This is a real-world situation where good practices and constant refactoring pay off. The new Mac app eventually works around the missing functionalities by dynamically replacing (supportsAppGroups is false in this case): UserDefaultsEnvironment with NETunnelEnvironment, which does IPC by sending messages to the System Extension, rather than sharing data through the App Group UserDefaults. They both implement the TunnelEnvironmentReader protocol, meant for reading VPN data from the app. public protocol TunnelEnvironmentReader: Sendable { func environmentValue&lt;T&gt;(forKey key: TunnelEnvironmentKey&lt;T&gt;) -&gt; T? where T: Decodable } KeychainNEProtocolCoder with ProviderNEProtocolCoder, which installs the VPN profiles without the keychain, using the Network Extension map in providerConfiguration. They both implement the NEProtocolCoder protocol, meant for VPN profiles serialization. public protocol NEProtocolCoder: Sendable { func protocolConfiguration(from profile: Profile, title: (Profile) -&gt; String) throws -&gt; NETunnelProviderProtocol func profile(from protocolConfiguration: NETunnelProviderProtocol) throws -&gt; Profile } Do you really need DI? You’d better follow the DI approach early in your software development, but not immediately, because you may never need to convert a code module into a pluggable dependency. If you foresee that your software will scale enough to justify such an abstraction, then start pulling out third parties from your concrete classes. By the way, in many corporate projects people use DI because “everybody does”, rather than understanding the real benefits and tradeoffs. The overhead of maintaining abstract layers, or even third parties, for entities that will always have one implementation is sadly laughable. Please, do not avoid concrete classes just because you read that on some book. As I said in the beginning, you shouldn’t use a solution for a problem you don’t have, and most projects don’t need bulky external third parties to deal with their trivial dependencies. If you wasted hours to track down the order and lifecycle of the objects that an app creates on launch, you know what I’m talking about. Sometimes, it really seems that some modern programmers don’t know how to initialize objects in the first place. In that case, rather than a dependency injection engine –whatever that means–, why not go back to the basics?]]></summary></entry></feed>