最初に
この記事は、Swiftのコードを書く上で、実際に役に立つ内容は書いてありません。
ただ、UnboundedRange
がどのように実装されているのかを考察することで、多少コーディングの幅が広がることはあるかもしれません。
そして「UnboundedRange
を“呼ぶ”」とはどういうことなのか、ということは最後まで読んでいただければわかるかと思います…。
そもそもUnboundedRange
とは
UnboundedRange
は、その名の通りboundの無いrangeのことで、簡単に言えば全範囲を表すものです。コード上では単に...
と書きます。
たとえば、let array: Array<Int> = [0, 1, 2, 3, 4]
という配列があったとき、array[...]
と書くと、[0, 1, 2, 3, 4]
というArraySlice<Int>
が生成されます。
UnboundedRange
はどのように実装されているのか
幸いにもSwiftはオープンソースなのでその実装を覗き見ることができます。Swift 4.1.2現在、UnboundedRange
の実装は次のようになっています(ソース):
public typealias UnboundedRange = (UnboundedRange_)->()
…おや?
UnboundedRange
は、「UnboundedRange_
型のオブジェクト(インスタンス)を引数にとって戻り値のない関数」の別名となっているだけでした。
UnboundedRange_
とは?
これもソースコードを覗き見てみましょう(ソース)。実装部分だけを抜き出すと次の通りになります:
public enum UnboundedRange_ {
public static postfix func ... (_: UnboundedRange_) -> () {
fatalError("uncallable")
}
}
UnboundedRange_
は、一つだけstatic関数を持つ空のenum
でした。
なぜUnboundedRange_
を用意する必要があるのか?
Swiftの言語仕様上(文法規則上)、...
はoperator(演算子)であってidentifier(識別子)にはなり得ません。すなわち、let ...: UnboundedRange = UnboundedRange()
みたいな書き方は絶対にできません。でも、他のrangeと同様にarray[...]
という書き方はしたいという想いを実現したいとします。となると、...
というoperatorをそのままsubscriptの引数に渡すしかありません。
operatorを引数にすることはできる
一方で、Swiftではoperatorを引数にすることはできます。Array
のsorted(by:)
なんかは有名なところでしょう:
array.sorted(by:>) // -> [4, 3, 2, 1, 0]
なので、array[...]
という書き方を実現したければ、...
という適当な関数をでっちあげればいいことになります。
適当な関数を用意するために
...
という関数が、そこらへんにありそうな関数(たとえば(Int) -> Int
とか)だと混乱を来す可能性があります。(たぶん)そのために、UnboundedRange_
は生まれたのです。実際のソースコードではUnboundedRange_
のstatic関数として...
が宣言されていますが、グローバルな領域でpublic postfix func ...(_:UnboundedRange_) {}
と記述しても大丈夫なはずです。
これで実現
あとはArray
側でsubscript(_:UnboundedRange)
(即ちsubscript(_:(UnboundedRange_) -> ())
)を実装すればarray[...]
と書けるようになります。
ということは
subscriptの引数に(UnboundedRange_) -> ()
を渡せば同じ効果が得られるので、次のようなこともできます。
func f(_:UnboundedRange_) {}
print(array[...] == array[f]) // -> true
したからといって何かあるわけではありませんが。
そして、UnboundedRange
を“呼ぶ”
これでやっとタイトルの内容に入ることになります。UnboundedRange
は関数ということが判明しました。関数であるからには、一度は呼んでみたい。そう思うのが人の子ではないでしょうか。
UnboundedRange
は(UnboundedRange_)->()
であるので、まずは引数として渡すUnboundedRange_
のインスタンスを生成することにしましょう。
UnboundedRange_
のインスタンスを作る?
UnboundedRange_
は上記の通り、空のenum
でした。空のenum
というのは、そもそも無駄なインスタンスを作らせないために用意するものです。即ちlet u = UnboundedRange_()
とはできないのです。
UnboundedRange_
のインスタンスだと偽る
素直にインスタンスを用意することができないことが分かりました。となると、unsafe系の関数を思いつくかと思います。即ち、C言語でいうキャストを(無理やり)行うのです。
たとえば、
let u = unsafeBitCast(0, to:UnboundedRange_.self)
は、どうでしょう?
…結論から言うと残念ながらできません。Fatal error: Can't unsafeBitCast between types of different sizes
というエラーを吐き出します。MemoryLayout<UnboundedRange_>.size
は0
なのでInt
のサイズとは異なるのです。
なので、しかたなく、こうします:
struct S {}
let u = unsafeBitCast(S(), to:UnboundedRange_.self) // No error
これならうまくいきました。
あとはu...
とすれば…
u... // -> "Fatal error: uncallable"
見事、Fatal error: uncallable
というエラーを吐いてクラッシュしました!
"uncallable"な関数をcallすることに成功したのです。
別解
構造体を用意したくないのならば次のようにもできます:
var i = 0
withUnsafePointer(to:&i) {
$0.withMemoryRebound(to:UnboundedRange_.self, capacity:1) {
let u = $0.pointee
u... // -> "Fatal error: uncallable"
}
}
まとめ
- 特定の演算子を引数にする実装を実現したければ、空の
enum
を用意して行うのが吉。 - unsafe系の関数を使えば、(
public
である限り)呼べない関数はない。