Why both nonisolated and sendable keywords?

There is just one keyword when either a type or function call is constrained to the main actor, @MainActor.

However for being able to operate across isolation boundaries, there are two keywords: Sendable for types, and nonisolated for functions. Why?

Because a nonisolated function is not being sent itself. The function operates on a single isolation domain, which is not the isolation domain of an actor (thus: non-isolated), even when the function is defined as a method on an actor type or a type that is isolated to a global actor like @MainActor.

Functions that can be sent between isolation domains are marked with the @Sendable attribute.

1 Like

Also, all methods on a struct/class are implicitly sort of @Sendable functions since they cannot close over any non-sendable local state (only self which is an implicit parameter to the function, and any globals which have to be Sendable).

A non-Sendable self is the killer though!

class NonSendable {
	func isThisSendable() {
	}

	func acceptsSendable(_ fn: @Sendable () -> Void) {
	}

	func letsCheck() {
		// error: converting non-sendable function value to '@Sendable () -> Void' may introduce data races
		acceptsSendable(isThisSendable)
	}
}
3 Likes