# When can one (safely) \`unsafeBitCast\` between function types?

**URL:** <https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324>\
**Category:** Using Swift\
**Tags:** type-casting, functions\
**Created:** [September 24, 2025, 1:44am UTC](https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324 "2025-09-24T01:44:39Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jamieQ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jamieq/32/5117_2.png) [@jamieQ](https://forums.swift.org/u/jamieQ)\
**Post date:** [September 24, 2025, 1:44am UTC](https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324/1 "2025-09-24T01:44:39Z")

</div>

related to the discussion [here](https://forums.swift.org/t/isolated-any-evolution-and-current-limitations/81923/18), i was wondering more generally: what does it mean to `unsafeBitCast` between two function types? are there cases in which doing so is ever 'actually safe' (and others where it never is)? if so, what are they?

my intuition is that any cast that adds/removes arguments or changes a function's parameters or return type in incompatible ways would be suspect. but what about doing things like adding/removing certain parameter attributes?

here is a [compiler explorer example](https://swift.godbolt.org/z/P4vbbYo5j) with some experiments i've tried (source also reproduced below).

> **source code**
>
> ```swift
> class A {
> var value: Int = 0
> }
> 
> class B: A {}
> 
> func ubc() {
> typealias FnOne = (Int) -> Void
> 
> let intFn: FnOne = { i in print("i: \(i)") }
> intFn(42) // i: 42
> 
> do {
> typealias NewFn = () -> Void
> let newFn: NewFn = unsafeBitCast(intFn, to: NewFn.self)
> newFn() // i: 72057602407836425
> }
> 
> do {
> typealias NewFn = (String) -> Void
> let newFn: NewFn = unsafeBitCast(intFn, to: NewFn.self)
> newFn("hello") // i: 478560413032
> }
> 
> typealias FnStr = (String) -> Void
> let strFn: FnStr = { s in print("s: \(s)") }
> strFn("hello world") // hello world
> 
> do {
> typealias NewFn = (Int) -> Void
> let newFn: NewFn = unsafeBitCast(strFn, to: NewFn.self)
> //newFn(42) // 💥 Fatal error: UnsafeBufferPointer has a nil start and nonzero count
> }
> 
> do {
> typealias NewFn = () -> Void
> let twoStr: NewFn = unsafeBitCast(strFn, to: NewFn.self)
> // twoStr() // 💥
> }
> 
> typealias ClassFn = (A) -> Void
> let classFn: ClassFn = { a in print("a: \(String(describing: a))") }
> classFn(A()) // a: A
> 
> do {
> typealias NewFn = () -> Void
> let newFn = unsafeBitCast(classFn, to: NewFn.self)
> // newFn() // 💥
> }
> 
> do {
> typealias NewFn = (B) -> Void
> let newFn: NewFn = unsafeBitCast(classFn, to: NewFn.self)
> newFn(B()) // a: B
> }
> 
> typealias IsoAnyFn = @isolated(any) (Int) -> Void
> let isoAnyFn: IsoAnyFn = { @MainActor int in
> MainActor.preconditionIsolated()
> print("main actor: \(int)")
> }
> 
> do {
> typealias NewFn = (Int) -> Void
> let newFn: NewFn = unsafeBitCast(isoAnyFn, to: NewFn.self)
> newFn(42) // main actor: 42
> }
> }
> ubc()
> 
> ```

---

<div class="post-metadata">

**Author:** ![SergeyPetrachkov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/sergeypetrachkov/32/36767_2.png) [@SergeyPetrachkov](https://forums.swift.org/u/SergeyPetrachkov)\
**Post date:** [September 24, 2025, 7:16am UTC](https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324/2 "2025-09-24T07:16:06Z")

</div>

That's what I love Swift forums for! Thanks @jamieQ for running an extra mile with this follow-up on the discussion 🙂

---

<div class="post-metadata">

**Author:** ![ph1ps](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ph1ps/32/35459_2.png) [@ph1ps](https://forums.swift.org/u/ph1ps)\
**Post date:** [September 24, 2025, 7:32am UTC](https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324/3 "2025-09-24T07:32:16Z")

</div>

> [@jamieQ](#):
>
> ```swift
> do {
> typealias NewFn = (Int) -> Void
> let newFn: NewFn = unsafeBitCast(isoAnyFn, to: NewFn.self)
> newFn(42) // main actor: 42
> }
> 
> ```

Not sure if this is what you intended to demonstrate, but this does not assert because top level code is implicitly main actor isolated. If you‘d change `ubc` to be `async` it will assert.

So you removed `@isolated(any)` unsafely and it made it unsafe.

---

<div class="post-metadata">

**Author:** ![jamieQ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jamieq/32/5117_2.png) [@jamieQ](https://forums.swift.org/u/jamieQ)\
**Post date:** [September 24, 2025, 9:08am UTC](https://forums.swift.org/t/when-can-one-safely-unsafebitcast-between-function-types/82324/4 "2025-09-24T09:08:32Z")

</div>

> [@ph1ps](#):
>
> Not sure if this is what you intended to demonstrate, but this does not assert because top level code is implicitly main actor isolated. If you‘d change `ubc` to be `async` it will assert.
> 
> So you removed `@isolated(any)` unsafely and it made it unsafe.

that's a good point, and that was perhaps not the best choice of example. but it is sort of the essence of the original discussion question – can you 'cast off' the `@isolated(any)` attribute from a synchronous, `@isolated(any)` function, by effectively taking over responsibility for the actor executor check yourself? this example shows it's at least a plausible thing to do (at least for global actor-isolated functions), but it's unclear to me if it's 'in practice safe'\[1\], or its appearing to 'work' is purely incidental.

* * *

1. assuming you insert the preconditions yourself, or somehow only invoke it when known to be on the correct actor
