# \[Pitch 4\] Custom Main and Global Executors

**URL:** <https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107>\
**Category:** Pitches\
**Created:** [August 21, 2026, 10:45am UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107 "2026-08-21T10:45:09Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![al45tair](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/al45tair/32/17985_2.png) [@al45tair](https://forums.swift.org/u/al45tair)\
**Post date:** [August 21, 2026, 10:45am UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/1 "2026-08-21T10:45:09Z")

</div>

This is a fourth pitch for [Custom Main and Global Excecutors](https://github.com/swiftlang/swift-evolution/pull/2654). The previous pitch included a delayed enqueuing proposal, which was separated out into [SE-0505: Delayed Encoding for Executors](https://forums.swift.org/t/se-0505-delayed-enqueuing-for-executors/84157).

Changes in this version:

- The `SchedulingExecutor` and all discussion of `Clock`s was removed.
- `ThreadDonationExecutor` was separated out from `RunLoopExecutor`, and `MainExecutor` now requires the former rather then the latter.
- The "Alternatives Considered" section was updated.

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [August 21, 2026, 1:45pm UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/2 "2026-08-21T13:45:16Z")

</div>

I continue to be in support of this proposal. A few minor comments:

> public static var preferredExecutor: (any TaskExecutor)? { get }

What executor is this returning? Is this returning the task executor preference set with the `withTaskExecutorPreference` or something else? If it is returning the former then I would prefer naming this `preferredTaskExecutor` instead and the documentation clearly the link between the two APIs.

> /// `true` if this is the main executor.  
> var isMainExecutor: Bool { get }

This only returns true if the given executor is the actual used main executor not only if it conforms to `MainExecutor`, right?

> ### Providing `Task.currentExecutor`
> 
> There is a concern that `currentExecutor` could in future be misused  
> to schedule jobs directly when using the `#isolation` feature would be  
> preferable (and potentially more performant).

I continue to think this concern is somewhat moot. The current executor in Swift right now is determined in this order: serial executor for the current isolation, task preferred executor, then the default executor. This proposal is adding API to retrieve the default and task preferred executor. The serial executor can already be extracted with existing public API by using a `nonisolated(nonsending)` method to get the `#isolation`. From the isolation you can get the actor, and on the actor you can call `withSerialExecutor` to get the executor. So user's can and most likely will replicate a `currentExecutor` method like this:

```swift
extension Task {
  static nonisolated(nonsending) func currentExecutor() async -> any Executor? {
    if let actor = #isolation {
      return actor.withSerialExecutor { $0 }
    } else if let taskPreferred = Task.taskPreferredExecutor {
      return taskPreferred
    } else {
      return Task.defaultExecutor
    }
  }
}

```

In my opinion, it's better if the Concurrency module provides such a method since it can be kept in-sync with any runtime changes to the executor logic.

---

<div class="post-metadata">

**Author:** ![al45tair](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/al45tair/32/17985_2.png) [@al45tair](https://forums.swift.org/u/al45tair)\
**Post date:** [August 24, 2026, 11:03am UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/3 "2026-08-24T11:03:08Z")

</div>

> [@FranzBusch](#):
>
> What executor is this returning? Is this returning the task executor preference set with the `withTaskExecutorPreference`… I would prefer naming this `preferredTaskExecutor` instead and the documentation clearly the link between the two APIs.

Yes. I think I prefer the currently proposed naming (`Task.preferredExecutor`) because `Task.preferredTaskExecutor` seems a little tautologous. I honestly don't think anyone will be confused by this.

> [@FranzBusch](#):
>
> This only returns true if the given executor is the actual used main executor not only if it conforms to `MainExecutor`, right?

That's certainly the intent, though some `MainExecutor`s might choose to simply return `true` if they have no other purpose.

> [@FranzBusch](#):
>
> > ### Providing `Task.currentExecutor`
> > 
> > There is a concern that `currentExecutor` could in future be misused  
> > to schedule jobs directly when using the `#isolation` feature would be  
> > preferable (and potentially more performant).
> 
> I continue to think this concern is somewhat moot.

I don't really want to re-open the debate on this at this point, as I think all that will do is further delay this proposal. It'll exist internally, because it's useful for the implementation, so if we want to make it public in future as part of a separate proposal, or if we want to expose it as SPI, we can do so.

---

<div class="post-metadata">

**Author:** ![louisowen2608](https://avatars.discourse-cdn.com/v4/letter/l/c77e96/32.png) [@louisowen2608](https://forums.swift.org/u/louisowen2608)\
**Post date:** [August 24, 2026, 1:08pm UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/4 "2026-08-24T13:08:22Z")

</div>

Thanks for the update. With `SchedulingExecutor` removed from this iteration, what is the current recommended path for APIs that rely on time-based or delayed dispatching under custom global executors?

---

<div class="post-metadata">

**Author:** ![al45tair](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/al45tair/32/17985_2.png) [@al45tair](https://forums.swift.org/u/al45tair)\
**Post date:** [August 24, 2026, 3:04pm UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/5 "2026-08-24T15:04:21Z")

</div>

> [@louisowen2608](#):
>
> Thanks for the update. With `SchedulingExecutor` removed from this iteration, what is the current recommended path for APIs that rely on time-based or delayed dispatching under custom global executors?

The `SchedulingExecutor` API surface was moved to [SE-0505](https://forums.swift.org/t/se-0505-delayed-enqueuing-for-executors/84157). There is [a separate pitch for the latest iteration of that](https://forums.swift.org/t/pitch-4-delayed-enqueuing-for-executors/87918).

---

<div class="post-metadata">

**Author:** ![Douglas\_Gregor](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/douglas_gregor/32/1362_2.png) [@Douglas\_Gregor](https://forums.swift.org/u/Douglas_Gregor)\
**Post date:** [August 31, 2026, 9:34pm UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/6 "2026-08-31T21:34:37Z")

</div>

> [@al45tair](#):
>
> This is a fourth pitch for [Custom Main and Global Excecutors](https://github.com/swiftlang/swift-evolution/pull/2654). The previous pitch included a delayed enqueuing proposal, which was separated out into [SE-0505: Delayed Encoding for Executors](https://forums.swift.org/t/se-0505-delayed-enqueuing-for-executors/84157).

I haven't been following this pitch closely in its previous iterations, but I've been in this same area as part of the [effort to improve concurrency for Embedded Swift](https://forums.swift.org/t/exploring-multicore-concurrency-for-embedded-swift/87129) as well as building out the [platform abstraction layer](https://forums.swift.org/t/introducing-the-embedded-swift-platform-abstraction-layer/89248) for it.

My primary concern is that the mechanism proposed introduces additional indirection that will be prohibitive for Embedded Swift clients, as well as adding overhead for the non-embedded case. The most direct place this shows up is in the definition of the `ExecutorFactory` protocol, which is using existentials for defining custom executors:

```swift
/// An ExecutorFactory is used to create the default main and task
/// executors.
public protocol ExecutorFactory {
  /// Constructs and returns the main executor, which is started implicitly
  /// by the `async main` entry point and owns the "main" thread.
  static var mainExecutor: any MainExecutor { get }

  /// Constructs and returns the default or global executor, which is the
  /// default place in which we run tasks.
  static var defaultExecutor: any TaskExecutor { get }
}

```

The use of existentials means we will always have indirect calls whenever we're scheduling work on an executor, which is both a direct cost and an optimization barrier. Instead, this protocol could capture the two executor types in an associated type:

```swift
public protocol ExecutorFactory {
  associatedtype MainExecutorType: MainExecutor
  static var mainExecutor: MainExecutorType { get }

  associatedtype DefaultExecutorType: TaskExecutor
  static var defaultExecutor: DefaultExecutorType { get }
}

```

The canonical way to conform to this protocol would be something like this:

```swift
struct MyExecutorFactory: ExecutorFactory {
  static var mainExecutor = MyMainExecutor()
  static var defaultExecutor = MyTaskExecutor()
}

```

where we now have the ability to globally allocate the instances that back the main and default executor.

The proposal also notes that the existing "hook functions" will still be there for use by Embedded Swift:

> As we are not proposing to remove the existing "hook function" API from Concurrency at this point, it will still be possible to implement an executor for Embedded Swift by implementing the `Impl` functions in C/C++.

I understand the desire to not bring these hooks into scope, but we should understand how the layers fit together before we add another potentially-incompatible one.

The existing hook mechanism is not great for Embedded Swift, or in general for static builds: you end up compiling in the default executors for the platform, then assigning some global function pointers to override them at runtime. So you pay an indirection (always) as well as the code-size cost for the default executor, and it's subject to mistakes if you assign at the wrong time.

The platform abstraction layer takes a different approach: the Swift code calls a set of pre-declared C entrypoints that aren't implemented in the Swift runtime at all. Instead, one links in an implementation of these C functions in the final binary. You get direct calls, no dead code from unused implementations, duplicate/conflicting overrides turn into link errors, and the ability to use LTO to "see through" the calls. The proposed mechanism for overriding the main and default executors can work along with this approach, by effectively spitting out `@c` entrypoints that call through the `DefaultExecutorFactory` where the `DefaultExecutorFactory` typealias is declared, e.g.,

```swift
@c
func _swift_task_enqueueGlobal(job: COpaquePointer) -> Void {
   DefaultExecutorFactory.defaultExecutor.enqueue(ExecutorJob(UnownedJob(job)))
}

```

The existing executors should be able to be linked in with the concurrency runtime and go through these hooks, or else we haven't expressed the whole of the interface.

For non-static builds, the Swift standard library has load-time mechanisms for replacing functions, and we should consider using it (or Swift's own dynamic-replacement machinery) rather than assigning to global function pointers, even if it means deprecating/removing the existing concurrency hooks. The module/translation unit defining the `DefaultExecutorFactory` for non-static builds should hook that mechanism.

Doug

---

<div class="post-metadata">

**Author:** ![al45tair](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/al45tair/32/17985_2.png) [@al45tair](https://forums.swift.org/u/al45tair)\
**Post date:** [September 1, 2026, 10:20am UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/7 "2026-09-01T10:20:28Z")

</div>

> [@Douglas\_Gregor](#):
>
> My primary concern is that the mechanism proposed introduces additional indirection that will be prohibitive for Embedded Swift clients, as well as adding overhead for the non-embedded case.

For non-embedded we already have to use existentials everywhere anyway because of the various other existing mechanisms for overriding executors (admittedly the existentials are often hidden behind C++ types like `SerialExecutorRef`/`TaskExecutorRef` and Swift types like `UnownedSerialExecutor`/`UnownedTaskExecutor`, but they're still existentials under the covers).

I don't know how Embedded Swift intends to handle `withTaskExecutorPreference(someExecutor) { ... }` or executors on actors without using existentials, but we should probably talk about that?

> [@Douglas\_Gregor](#):
>
> Instead, this protocol could capture the two executor types in an associated type:
> 
> ```swift
> public protocol ExecutorFactory {
> associatedtype MainExecutorType: MainExecutor
> static var mainExecutor: MainExecutorType { get }
> 
> associatedtype DefaultExecutorType: TaskExecutor
> static var defaultExecutor: DefaultExecutorType { get }
> }
> 
> ```

This seems like a reasonable ask, although I have questions about how much this is really going to buy us. We would know the concrete types of the default executors, but if they happen to not be the current executor for some reason then we're surely back to existentials?

> [@Douglas\_Gregor](#):
>
> The proposal also notes that the existing "hook functions" will still be there for use by Embedded Swift

Not _just_ Embedded Swift; while I did mention that specifically, they are also actively used by various things today, so we really had to keep them working, at least until this work has landed and people have switched over to it.

> [@Douglas\_Gregor](#):
>
> The existing hook mechanism is not great for Embedded Swift, or in general for static builds

I'd go further — I'm no fan of it for _any_ kind of build, Embedded or not. I understand why it was added, but I would be very happy to see it removed. However, as I say, I'm led to believe that there are things that rely on it today, and so I didn't want to remove it as part of this work.

> [@Douglas\_Gregor](#):
>
> The platform abstraction layer takes a different approach: the Swift code calls a set of pre-declared C entrypoints that aren't implemented in the Swift runtime at all.

This is very much in the spirit of the `ExecutorImpl.h` header that I'd previously added, which — I think — provides exactly such an interface, with the Swift side of that in `ExecutorImpl.swift`, which doesn't get linked into the Embedded Swift Concurrency library.

---

<div class="post-metadata">

**Author:** ![al45tair](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/al45tair/32/17985_2.png) [@al45tair](https://forums.swift.org/u/al45tair)\
**Post date:** [September 15, 2026, 2:21pm UTC](https://forums.swift.org/t/pitch-4-custom-main-and-global-executors/89107/8 "2026-09-15T14:21:49Z")

</div>

Update: I've updated the proposal to add the `associatedtype`s @Douglas_Gregor asked for above, and also fixed the implementation to include those.
