Краткий ответ:
Приведение типа C в основном означает, что компилятор Swift просто заставит ваше замыкание вызываться так, как если бы он принимал кортеж (Int, Float)
в качестве параметра, тогда как as / as?/ как!бросок сначала сделает некоторые проверки работоспособности на вашем броске, чтобы гарантировать, что типы совместимы и так далее.
Поскольку компилятор считает (в некоторых версиях, как видно из комментариев к другому ответу), что (Int, Float) -> ()
и ((Int, Float)) -> ()
слишком далеко друг от друга, чтобы быть совместимыми, проверка работоспособности просто вернет ноль, поэтомублокировка вашего вызова.
Что делает его работающим, так это то, что функция / замыкание, принимающее кортеж (Int, Float)
, ведет себя точно так же (в текущей версии Swift), что и функция / замыкание, принимающее Int
иFloat
параметр.
Длинный ответ:
Я собрал фрагмент кода в сборку, на которую я буду ссылаться с этого момента.Этот фрагмент можно найти здесь: https://swift.godbolt.org/z/CaOb0s
Для удобства чтения я использовал здесь функции вместо реальных замыканий.
Я создал две функции, соответствующие двум имеющимся у нас случаям:
func twoParamFunc(a: Int, b: Float)-> Void {
print(a, b)
}
func singleParamFunc(tuple: (a: Int, b: Float))-> Void {
print(tuple.a, tuple.b)
}
Затем я попытался привести их, используя два разных метода:
let cCastFunction = ((((Int, Float)) -> Void)?(twoParamFunc))!
let asCastFunction = (twoParamFunc as? (((Int, Float)) -> Void))!
И, глядя на код сборки, скомпилированный с помощью swift, мы можем увидеть много различий между этими двумя,
Рассматривая приведение в стиле C, мы видим, что большая часть кода в основном просто вызывает alloc / retain / release и перемещает указатели и значения вокруг.Единственный вызов внешнего кода происходит через случай сбоя (разыменование !
пустой ссылки), вызывая $ss18_fatalErrorMessage__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF
В то время как в приведении в стиле swift, есть много дополнительных вызовов (здравомыслиечеки, о которых я говорил ранее).У нас есть, например,
call (type metadata accessor for (Swift.Int, Swift.Float) -> ())
...
call (type metadata accessor for ((Swift.Int, Swift.Float)) -> ())
...
call swift_dynamicCast@PLT
, который ясно показывает, что компилятор Swift выполняет некоторые проверки совместимости типов приведения и нигде не встречается в приведении в стиле c.
Так что теперь, когда была найдена разница между приведением типов в стиле C и Swift, мы можем попытаться понять, почему работает вызов приведенной функции стиля C.
При просмотре сгенерированного кода сборкис помощью двух простых вызовов функций, которые я сделал в образце:
twoParamFunc(a: a.0,b: a.1)
singleParamFunc(tuple: a)
Мы можем видеть, что эти функции на самом деле скомпилированы так, чтобы вызываться одинаково:
singleParamFunc
:
mov rdi, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
call (output.singleParamFunc(tuple: (a: Swift.Int, b: Swift.Float)) -> ())
Здесь мы видим, что значение, соответствующее первому значению кортежа, заносится в регистр rdi
, а второе - в xmm0
, а затем функция вызывается
* 1053.*
twoParamFunc
:
mov rax, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
...
mov rdi, rax
...
call (output.twoParamFunc(a: Swift.Int, b: Swift.Float) -> ())
В этой функции это не так просто, но теперь значение 1 заносится в регистр rax
, который сам копируется в регистр rdi
, а значение 2 все еще входит в регистрxmm0
и вызывается функция.
Но в этом примере, поскольку мы занимаемся другими вещами, ассемблерный код немного запутан, я сделал еще один пример для его проверки: https://swift.godbolt.org/z/vDCZZV
В этом примере (к которому я добавил еще один тест со структурой) мы видим, что код сборки, созданный для вызова трех функций, абсолютно одинаков:
mov rdi, qword ptr [rip + (output.structValue : output.struct_test)]
movss xmm0, dword ptr [rip + (output.structValue : output.struct_test)+8]
call (output.test(value: output.struct_test) -> ())
mov rdi, qword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))+8]
call (output.test2(tuple: (Swift.Int, Swift.Float)) -> ())
mov ecx, 1
mov edi, ecx
movss xmm0, dword ptr [rip + .LCPI0_0]
call (output.test3(a: Swift.Int, b: Swift.Float) -> ())
Чтобы возобновить, в текущей версии swift, любая из этих трех функций может быть c-приведена к любой другой и все еще работает.
Это оказалось намного дольше, чем первоначально планировалось, но я подумал, что этопроблема это заслужила.