-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathusing-silgenname-to-call-private-swift-code.html
313 lines (271 loc) · 21.4 KB
/
using-silgenname-to-call-private-swift-code.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="Let me start this post with a disclaimer. The trick I'm going to show here is quite powerful, but much like every other underscored attribute in Swift, this is something you should not mess with unless you know exactly what you're doing.">
<meta name="title" content="Using @_silgen_name to forward declare functions in Swift and improve build times">
<meta name="url" content="https://swiftrocks.com/using-silgenname-to-call-private-swift-code">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Using @_silgen_name to forward declare functions in Swift and improve build times"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="Let me start this post with a disclaimer. The trick I'm going to show here is quite powerful, but much like every other underscored attribute in Swift, this is something you should not mess with unless you know exactly what you're doing."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/using-silgenname-to-call-private-swift-code"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Using @_silgen_name to forward declare functions in Swift and improve build times"/>
<meta name="twitter:description" content="Let me start this post with a disclaimer. The trick I'm going to show here is quite powerful, but much like every other underscored attribute in Swift, this is something you should not mess with unless you know exactly what you're doing."/>
<meta name="twitter:site" content="@rockbruno_"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="images/favicon/iconsmall2.png" sizes="32x32" />
<link rel="apple-touch-icon" href="images/favicon/iconsmall2.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<link rel="canonical" href="https://swiftrocks.com/using-silgenname-to-call-private-swift-code"/>
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism4.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style48.css">
<link rel="stylesheet" type="text/css" href="css/sponsor4.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/using-silgenname-to-call-private-swift-code"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2024-03-14T14:00:00+02:00",
"dateModified": "2024-03-14T14:00:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb.jpg"
}
},
"headline": "Using @_silgen_name to forward declare functions in Swift and improve build times",
"abstract": "Let me start this post with a disclaimer. The trick I'm going to show here is quite powerful, but much like every other underscored attribute in Swift, this is something you should not mess with unless you know exactly what you're doing."
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2light.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Using @_silgen_name to forward declare functions in Swift and improve build times-->
<!--WRITEIT_POST_HTML_NAME=using-silgenname-to-call-private-swift-code-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=Let me start this post with a disclaimer. The trick I'm going to show here is quite powerful, but much like every other underscored attribute in Swift, this is something you should not mess with unless you know exactly what you're doing.-->
<!--DateFormat example: 2021-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2024-03-14T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2024-03-14T14:00:00+02:00-->
<title>Using @_silgen_name to forward declare functions in Swift and improve build times</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Using <code>@_silgen_name</code> to forward declare functions in Swift and improve build times</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 14 Mar 2024</div>
</div>
<blockquote>Disclaimer: The trick I'm going to show here is quite powerful, but like every other underscored attribute in Swift, this is something you should avoid messing with unless you know exactly what you're doing. There are lots of pitfalls attached to these attributes, and the behavior of underscored attributes can change at any time and even stop existing entirely without warning. Don't go around sprinkling this in your projects if you don't fully understand the consequences of doing so!</blockquote>
<p>Swift is regarded for its type safety, meaning the compiler (usually) doesn't allow you to reference or do things that might potentially not exist / be invalid; the complete opposite of languages like Obj-C where the compiler allows you to do pretty much whatever you want in exchange for compile-time safety.</p>
<p>But here's an obscure fact about Swift: The language <i>does</i> support ObjC-like <code>Selectors</code> / forward declarations, it's just that we're not supposed to use it. If you know how a function is going to be named in the compiled binary, you can use the <code>@_silgen_name</code> attribute to craft a <b>direct reference</b> to that function, allowing a module to reference and call it regardless of whether or not it actually has "visibility" of it:</p>
<pre><code>@_silgen_name("somePrivateFunctionSomewhereThatICantSee")
func refToThatFuncIReallyWantToCall()
func foo() {
refToThatFuncIReallyWantToCall() // Just called something I wasn't suposed to be able to!
}</code></pre>
<p>This is used extensively by the Swift standard library to create something akin to the old-school <b>forward declarations</b> in Obj-C / C, allowing it to call functions that live deeper in the Swift Runtime even though it shouldn't be able to. As denoted by the underscore, this is <b>not</b> an official feature of Swift, but rather an internal detail of the compiler that <b>is not meant to be used outside of this specific internal case.</b> Nonetheless, you <i>can</i> use it in your regular Swift apps, so if you know what you're doing and is aware of the consequences / implications, you can do some pretty neat stuff with it.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<h2><code>@_silgen_name</code> and symbol mangling</h2>
<p>Since Swift has namespacing features, the names you give to your Swift functions are not actually what they will be called in the compiled binary. To prevent naming collisions, Swift injects a bunch of context-specific information into a function's symbol that allows it to differentiate it from other functions in the app that might have the same name, in a process referred to as <b>symbol mangling</b>:</p>
<pre><code>// Module name: MyLib
func myFunc() { print("foo") }</code></pre>
<pre class="command-line language-bash" data-output="3-4"><code>swiftc -emit-library
nm libMyLib.dylib
MyLib.myFunc()'s "real" name is:
$s5MyLib6myFuncyyF</code></pre>
<p>What <code>@_silgen_name</code> does under the hood is <b>override a function's mangled symbol</b> with something of your choosing, giving us the ability to reference functions in ways that Swift generally wouldn't allow us to (which I'll show further below).</p>
<p>The attribute can be used in two ways: to override the symbol of a declaration and to override the symbol of a <i>reference</i> to a declaration. When added to a <b>declaration</b>, as in, a function with a body, the attribute overrides that function's mangled name with whatever it is that you passed to the attribute:</p>
<pre><code>@_silgen_name("myCustomMangledName")
func myFunc() { print("foo") }</code></pre>
<pre class="command-line language-bash" data-output="3-4"><code>swiftc -emit-library -module-name MyLib test.swift
nm libMyLib.dylib
MyLib.myFunc()'s name now is:
myCustomMangledName</code></pre>
<p>This is interesting, but what we truly care about here is what happens when you add it to a function that <b>doesn't</b> have a body. This would usually be invalid Swift code, but because we've added <code>@_silgen_name</code> to it, the compiler will treat it as valid code and assume that this function is <i>somehow</i> being declared somewhere else under the name we passed to the attribute, effectively <b>allowing us to build forward declarations in pure Swift:</b></p>
<pre><code>@_silgen_name("$s5MyLib6myFuncyyF")
func referenceToMyFunc()
func foo() {
// Successfully compiles and calls MyLib.myFunc(), even though
// this module doesn't actually import the MyLib module
// that defines myFunc()
referenceToMyFunc()
}</code></pre>
<p>(This only works if the "target" is a free function, so for things like a class's static functions you'll need to first define a function that wraps them.)</p>
<p>Now, it should be noted that knowing a Swift function's mangled name in advance (<code>$s5MyLib6myFuncyyF</code>, in the above example) is not straight-forward as the compiler doesn't expose an easy way of predicting what these values will be, but we can fix this by using <code>@_silgen_name</code> on the declaration itself in order to modify it to something that we know and is under our control, like in the previous example where we replaced it with <code>"myCustomMangledName"</code>. Note that you only need to worry about this when referencing Swift functions; For Obj-C / C, a function's "mangled name" will be the function's actual name as those languages have no namespacing features.</p>
<pre><code>@_silgen_name("myCustomMangledName")
func referenceToFooMyFunc()</code></pre>
<p>It's critical to note that this is extremely <b>unsafe</b> by Swift compiler standards as it sidesteps any and every type safety check that would normally apply here. <b>The compiler will not run any validations here</b>; it will instead completely trust that you know <i>exactly</i> what you're doing, that somehow these functions will exist in runtime even though this doesn't seem to be the case during compilation, that any custom names you're using are unique and not causing any potential conflicts with other parts of the codebase, and that whatever parameters you're passing to the forward-declared functions are correct and managed properly memory-wise (if your target is a C function, you need to do manual memory management with <code>Unmanaged<T></code>).</p>
<p>If everything is done correctly, you just got yourself a nice forward-declared function, but if not, you'll experience undefined behavior. You <i>do</i> get a compile-time linker error though if the functions don't exist at all, which is pretty handy as I've noticed that in addition to all of the above concerns, the compiler also may sometimes accidentally tag these functions as "unused" depending on how you declare them, causing them to be stripped out of the compiled binary when they should not. I am sure that there are way more things that can go wrong here that I'm not aware of.</p>
<h2>Cool, but why?</h2>
<p>Lack of safety aside, there are two situations where I find this attribute useful outside the Swift standard library. The first one is being able to do <b>C interop</b> without having to define annoying headers and imports, similar to how the Swift standard library has been using it. It seems that a lot of people have been doing this, but I'll not cover this here because it's not the use case that led me to use this attribute. I'll just point out that this is something you also need to be very careful about, particularly because <code>@_silgen_name</code> functions use the Swift calling convention, which is incompatible with C (thanks Ole Begemann for pointing that out!).</p>
<h3>Trading safety for better build times</h3>
<p>The second one however, which is what I have been using this for, is that when applied strategically, you can use this attribute to <b>greatly improve your app's incremental build times.</b></p>
<p>Let's assume that we're developers of a large modularized Swift app that has some sort of type safe dependency injection mechanism to pass values around. For this mechanism to work, we might end up with a "central" registry of dependencies that imports every module and configures every possible dependency these modules might request:</p>
<pre><code>import MyDepAImplModule
import MyDepBImplModule
import MyDepCImplModule
...
func setupRegistry() {
myRegistry.register(MyDepA(), forType: MyDepAProtocol.self)
myRegistry.register(MyDepB(
depA: myRegistry.depA,
), forType: MyDepBProtocol.self)
myRegistry.register(MyDepC(
depA: myRegistry.depA,
depB: myRegistry.depB,
), forType: MyDepCProtocol.self)
}</code></pre>
<p>Something like this allows us to have a nice and safe system where features are unable to declare dependencies that don't exist, but it will come at the cost of <b>increased incremental build times.</b> Importing all modules like this will cause this module to be constantly invalidated, and the bigger your project gets, the worse this problem will get. In my personal experience, projects with a setup like this and with several hundred modules can easily end up with a massive <b>10~60 seconds delay</b> to incremental builds, depending on the number of modules and how slow your machine is.</p>
<p>However, by using forward-declared <code>@_silgen_name</code> references to a function that wraps the initializers instead of referencing these initializers directly, we can achieve the same injection behavior <b>without having to import any of the modules that define said initializers!</b></p>
<pre><code>@_silgen_name("myDepAInitializer") func makeMyDepA()
@_silgen_name("myDepBInitializer") func makeMyDepB(_ depA: MyDepAProtocol)
@_silgen_name("myDepCInitializer") func makeMyDepC(_ depA: MyDepAProtocol, _ depB: MyDepBProtocol)</code></pre>
<p>This allows projects like this to completely eliminate these build time bottlenecks, but it comes at the price of losing all type safety around this code. This might sound like a bad trade-off since type safety is the reason why a developer would want to have a dependency injection setup like this in the first place, but if you have other ways of validating those types and dependencies (such as a CLI that scans your app and automatically generates / validates this registry), you can abstract the dangerous bits away from your developers and effectively enjoy all the build time improvements without having to worry about any negatives other than having to be extra careful when making changes to this part of the code.</p>
<h2>Conclusion</h2>
<p>Forward-declaring Swift functions allow you to do all sorts of crazy things, but remember, <b>this is not an official feature of the language</b>. As mentioned in the beginning, my recommendation is that you should avoid messing with internal compiler features unless you're familiar with how Swift works under the hood and know exactly what you're doing.</p>
<p>But putting this aside, one thing that I tend to reflect on when learning about features like this is how the danger involved in using them is not so much about the features themselves, but rather that their behavior might change without warning.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Although I understand the Core team's vision of making Swift a safe and predictable language, I think there is a real demand for having poweruser-ish / "I know this is dangerous, I don't care" features like this officially supported in Swift, and it would be amazing if <code>@_silgen_name</code> could be recognized as one such feature. I like what you can achieve with it, and I would love to be able to use it without fear that it might change or stop existing in the future.</p>
</div>
</div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism4.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>