はじめに
以前に別の記事で、「public
である限り呼べない関数はない」と書いたのですが…。
public
でも呼び出せない関数がありそうなのです…。
呼べない関数…
それは次のようなコードです:
public protocol P {}
public protocol Q {}
extension P where Self: Q {
public func uncallable() { print("\(#function) in P.") }
}
extension Q where Self: P {
public func uncallable() { print("\(#function) in Q.") }
}
public struct S: P, Q {}
S().uncallable() // ⛔️ error: ambiguous use of 'uncallable()'
「プロトコルP
に準拠していて且つQ
にも準拠している場合」に呼び出せるはずの関数と、「プロトコルQ
に準拠していて且つP
にも準拠している場合」に呼び出せるはずの関数を、同名で定義すると、どちらの関数も呼び出せなくなってしまうのです!(当たり前)
でも、これ、S().uncallable()
の行が無ければコンパイルは通ってしまうんですよね。warningぐらい出してくれてもいいような気がするのですが…。
意地でも呼ぶ!(良い子は真似しちゃダメ)
呼べないと思っても悪あがきはしたい!
ということで、まずはコンパイルが通るようにS().uncallable()
は削除します:
public protocol P {}
public protocol Q {}
extension P where Self: Q {
public func uncallable() { print("\(#function) in P.") }
}
extension Q where Self: P {
public func uncallable() { print("\(#function) in Q.") }
}
public struct S: P, Q {}
さて、Swiftのコード上では区別して呼び出せない1uncallable()
ですが、これでコンパイルが通るということは、それぞれの関数は別々の名前にマングリングされているはずです2。
そこに一縷の望みを託しましょう!
どんな風にマングリングされているのかな?
Appleのドキュメントにマングリング規則が載ってるよ!3
ただし、Swift 4.x現在、ABIはunstableなので、今後変更される可能性があり注意が必要です。今回は、Swift 4.2(Apple Swift version 4.2 (swiftlang-1000.0.29.2 clang-1000.10.39)
)で試してみました。違うバージョンではマングリングされた名前が違うかもしれません。
何はさておきコンパイル
$ swiftc uncallable.swift -ouncallable
これでuncallable
という実行可能ファイルが出来上がります。ちなみに、ライブラリとしてコンパイルするとモジュール名などを含んでマングリングされるので、そうしたい場合は今後の記述の該当部分を都度読み替えてください。
マングリングを確認
念のためnm
を使ってシンボルがどうなっているか確認します。自分の環境では次のようになりました:
$ nm uncallable
00000001000010a0 S _$S10uncallable1PMp
0000000100000a70 T _$S10uncallable1PPA2A1QRzrlEAAyyF
0000000100000f34 s _$S10uncallable1P_pMF
00000001000010f8 S _$S10uncallable1QMp
0000000100000c00 T _$S10uncallable1QPA2A1PRzrlEAAyyF
0000000100000f44 s _$S10uncallable1Q_pMF
0000000100001070 S _$S10uncallable1SVAA1PAAMc
0000000100001080 S _$S10uncallable1SVAA1PAAWP
0000000100000da0 T _$S10uncallable1SVAA1PAAWa
0000000100001088 S _$S10uncallable1SVAA1QAAMc
0000000100001098 S _$S10uncallable1SVAA1QAAWP
0000000100000db0 T _$S10uncallable1SVAA1QAAWa
0000000100000d90 t _$S10uncallable1SVACycfC
0000000100000f54 s _$S10uncallable1SVMF
0000000100000dc0 T _$S10uncallable1SVMa
0000000100001150 s _$S10uncallable1SVMf
0000000100000f80 S _$S10uncallable1SVMn
0000000100001158 S _$S10uncallable1SVN
0000000100000f70 s _$S10uncallableMXM
U _$SSS19stringInterpolationS2Sd_tcfC
U _$SSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC
U _$SSS26_builtinUTF16StringLiteral18utf16CodeUnitCountSSBp_BwtcfC
U _$SSS26stringInterpolationSegmentSSx_tcs23CustomStringConvertibleRzs20TextOutputStreamableRzlufCSS_Tg5
U _$SSSN
U _$Ss27_allocateUninitializedArrayySayxG_BptBwlFSS_Tg5
U _$Ss27_allocateUninitializedArrayySayxG_BptBwlFyp_Tg5
U _$Ss5print_9separator10terminatoryypd_S2StF
U _$Ss5print_9separator10terminatoryypd_S2StFfA0_
U _$Ss5print_9separator10terminatoryypd_S2StFfA1_
U _$SytWV
0000000100000f98 s ___swift_reflection_version
0000000100000000 T __mh_execute_header
0000000100000a60 T _main
U _swift_bridgeObjectRelease
U _swift_bridgeObjectRetain
0000000100000f2e s _symbolic ____ 10uncallable1SV
0000000100000f0a s _symbolic $S10uncallable1PP
0000000100000f1c s _symbolic $S10uncallable1QP
U dyld_stub_binder
$S
から始まっているものは全てSwiftに関する型や関数などがマングリングされたものです。
このうち、結論から言うと、$S10uncallable1PPA2A1QRzrlEAAyyF
がextension P where Self: Q
のuncallable()
で、$S10uncallable1QPA2A1PRzrlEAAyyF
がextension Q where Self: P
のuncallable()
です。
直接呼べるか試してみよう!(このステップは省略可)
まずは、みんな大好きC言語で呼び出しを試みましょう。"wanna_call.c"というファイルを用意します。dlopen
を使えば簡単4:
# include <dlfcn.h>
struct S {};
struct S dummy = {};
int main(int argc, char *argv[]) {
void *handle = dlopen("./uncallable", RTLD_NOW);
if (handle == 0) { return 1; }
void (*uncallable_in_P)(struct S) = dlsym(handle, "$S10uncallable1PPA2A1QRzrlEAAyyF");
uncallable_in_P(dummy);
void (*uncallable_in_Q)(struct S) = dlsym(handle, "$S10uncallable1QPA2A1PRzrlEAAyyF");
uncallable_in_Q(dummy);
dlclose(handle);
return 0;
}
dlopen
で実行可能ファイルuncallable
をライブラリとして読み込んで、dlsym
を使ってそれぞれマングリングされた名前で関数ポインタを取得し、呼び出します5。
これをコンパイルして…
$ clang wanna_call.c -owanna_call
実行すれば…
$ ./wanna_call
uncallable() in P.
uncallable() in Q.
できました!
C言語からSwiftの"uncallable"な関数をcallできました!
Swiftで直接呼ぶ!
Swiftでマングリングされた関数を直接呼ぶ方法があるのか?
それが、あるのです。
昔の記事でも紹介した@_silgen_name
というattributeを使います。
@_silgen_name
を使えば、シンボル名を直接指定することができるのです。
具体的には、"uncallable.swift"を次のようにしてみましょう:
public protocol P {}
public protocol Q {}
extension P where Self:Q {
public func uncallable() { print("\(#function) in P.") }
}
extension Q where Self:P {
public func uncallable() { print("\(#function) in Q.") }
}
public struct S: P, Q {}
@_silgen_name("$S10uncallable1PPA2A1QRzrlEAAyyF")
public func uncallableInP<T>(_ obj:T) -> Void where T:P & Q
@_silgen_name("$S10uncallable1QPA2A1PRzrlEAAyyF")
public func uncallableInQ<T>(_ obj:T) -> Void where T:Q & P
uncallableInP(S())
uncallableInQ(S())
こうすることで、func uncallableInP(_:)
は$S10uncallable1PPA2A1QRzrlEAAyyF
というシンボルの関数を指すことになり、func uncallableInQ(_:)
は$S10uncallable1QPA2A1PRzrlEAAyyF
というシンボルの関数を指すことになります。
なお、@_silgen_name
のattributeを付けた関数は本体を書くこともできますが、今回は別に本体があるので、もちろん省略します。
あとはそれぞれS
のインスタンスをuncallableInP(_:)
とuncallableInQ(_:)
の引数にして呼び出せば完璧です。まぁ、このコードなら何を渡してもいいんですが。
このコードで、コンパイルをし直して…
$ swiftc uncallable.swift -ouncallable
実行すれば…
$ ./uncallable
uncallable() in P.
uncallable() in Q.
できました!
でも、これ、モジュール名を付けたり変えたりするたびにシンボル名も変更しなきゃいけないので、常用はできないですね。
おわりに
自分で「呼べない関数」を作って、最後には呼んでしまうという企画倒れ的な記事でしたが、Swiftも頑張ればそれなりに自由な設計ができそうだということが実感できましたよ。
…そもそも最初のuncallable()
が本当に普通に呼び出せないのかどうかが怪しいのですが。