// Calling this function
func resolve<Service: Sendable>() async throws -> Service
// Specify that we want service "Foo"
// Unexpected Generic cast to <Optional(Foo)>
let service: Foo = try await self?.servicesProvider.resolve()
// This middle layer forces Generic to resolve as <Foo>
actor ServicesProvider {
var foo: Foo {
get async throws {
try await resolve()
}
}
}
// Safe resolve where Generic always converts to <Foo>
let service = try await self?.servicesProvider.foo
I could not find anywhere in the the-swift-programming-language/generic that it's the Caller and not the Definition that casts Generic to a function.
// Would expect the definition of "let service: Foo" to be responsible for Generic casting
let service: Foo = try await self?.servicesProvider.resolve()
let service: Foo // <-- Expect this to cast to "func resolve<Foo>() -> async throws Foo"
= try await self?.servicesProvider.resolve() // <-- actually the Caller casts Generic which converts to Optional<Foo> in the resolve func
Yes with the unintuitive generic downcast we refactored to never use generic functions with optional self. I would still like to try to understand this behaviour so let me further specify why I'm confused.
I did not refer to the return value as yes that's Optional Foo i referred to the Generic Type casted in resolve() which looks like this: func resolve<Service: Sendable>() async throws -> Service
Example:
@MainActor
final class AppFooViewModel: ObservableObject {
@Published private(set) var state = .resolving
private let servicesProvider: any ServicesProvider
private var fooStreamTask: Task<Void, any Error>?
init(services: ServicesProvider) {
servicesProvider = services
resolveServices()
}
deinit { fooStreamTask?.canel() }
func resolveServices() {
fooStreamTask?.cancel()
fooStreamTask = Task { [weak self] in
do {
// ### Example 1. Does not resolve <Service: Sendable>
// - Compare this downcast
guard let foo: Foo = try await self?.servicesProvider.resolve() else {...}
// ### Example 2. Resolves <Service: Sendable>
// - With this "reference downcast"
guard let foo = try await self?.servicesProvider.foo else {...}
// (Legacy code) Reason for not guarding strong self to resolve service
// Async stream setup...
for await bar in foo.barStream {
guard let self else { return }
self.state = .active(bar.fooStep)
}
} catch {
await Fallback.missingService(Foo.self)
}
}
}
}
In these two examples I'm confused to why Example 2 works but not Example 1 when
in both cases the resolve() is coming from an optional wrapper self?
Example 1 does not downcast <Foo: Sendable> to resolve() when:
Type is defined by a parameter clause "let foo: Foo ="
Example 2 correctly downcasts <Foo: Sendable> to resolve() when:
Type is defined in the initialized clause " = try await self?.servicesProvider.foo"
Looking at the ServicesProvider protocol:
protocol ServicesProvider {
var servicesResolver: ServicesResolver { get }
func resolve<Service: Sendable>() async throws -> Service {}
}
extension ServicesProvider {
// This is what makes Example 2 safely resolve Generic as:
// resolve<Foo: Sendable>() async throws -> Foo {}
var foo: Foo {
get async throws { try await resolve() }
}
func resolve<Service: Sendable>() async throws -> Service {
try await servicesResolver.resolve()
}
}
Describing the difference in AppServicesResolver:
final actor AppServicesResolver: ServicesResolver {
func resolve<Service: Sendable>() async throws -> Service {
let name = String(describing: Service.self)
// ### Example 1:
// Service.self == ((Foo & AnyObject)?.Type) (Foo & AnyObject)?
// name == "Optional<Foo>"
// ###
// ### Example 2:
// Service.self == ((Foo & AnyObject).Protocol) Foo & AnyObject
// name == "Foo"
// ###
guard let service = try await resoveBy(name: name) as? Service else {
// Example 1: No Service named "Optional<Foo>"
throw ServiceProviderError.couldNotResoveBy(name: name)
}
// ...
return service
}
private func resolveBy(name: String) async throws -> any Sendable {...}
}
Sorry for long code when simply trying to differentiate the distinction between defining Type on the parameter side contrary on the initialize side when calling a generic function.
Similar distinction could be summarized in short with asyncFlatMap where
both examples expect to return Optional Foo but only the foo2 is successfully resolved.
// Non working result similar to Example 1.
guard let foo1: Foo = try await self.asyncFlatMap({ try await $0.servicesProvider.resolve() }) else {...}
// Working result similar to Example 2.
guard let foo2 = try await self.asyncFlatMap({ try await $0.servicesProvider.resolve() as Foo }) else {...}
In my mind the func resolve<Service: Sendable>() async throws -> Service should have gotten the "Defined Type" in both cases -> Foo and not be affected by any "wrappers".
Why does Swift handle the Type definition differently in these cases?
This distinction is what I could not find in "the-swift-programming-language/generic"
Edit:
Also what is the logic here if Service would resolve:
func resolve<Service: Sendable>() async throws -> Service {...}
// ### Example 1:
// Service.self == ((Foo & AnyObject)?.Type) (Foo & AnyObject)?
// name == "Optional<Foo>"
// Would it be correct that T == <Optional<Service>> here?
// ###
// I'm interpreting that resolve would look like this:
func resolve<Optional<Service>>() async throws -> <Optional<Service>> {...}
// What would happen here when Foo != Optional<Foo>
guard let foo: Foo = try await self?.servicesProvider.resolve() else {...}
Using this code above, in the Task I posted, does what you want, as does:
guard let explicit: Foo = try await self?.servicesProvider.resolve() else { return }
guard let cast = try await self?.servicesProvider.resolve() as Foo? else { return }
I would try it your more complex example, but you haven't pasted enough code to compile. It would be useful if you could whittle things down to a minimal reproducible example.
I see. The problem revolves around needing to have an unwrapped optional for its metatype string (which I don't recommend, but is workable). You can't get that with if let or guard let, which will perform optional promotion because of this:
My two best ideas for you, other than, again, never using self?, are:
if let self,
case let service: any FooService = try await servicesResolver.resolve() {
if let service = try await self?.servicesResolver.resolve((any FooService).self)
That said, your dictionary will never return nil. You can't unwrap the result. "("Service not found")" is not a correct representation of what's going on there. This is:
do {
guard let self else { print("Self (not service) not found"); return }
let service: any FooService = try await servicesResolver.resolve()
print("Got the service \(service)")
} catch {
print("Got error \(error)")
}
Thank you
This was the promotion I was confused about and now I see!
Also to answer my own question:
What would happen here when let foo: Foo != Optional<Foo>?
Answer: guard let of course handles this.
// This works fine when:
guard let service: FooService = try await self?.servicesResolver.resolve() else {...}
// Prints: "Got the service Foo"
print("Got the service \(service)")
// Hardcoding serviceName to "FooService":
func resolve<Service: Sendable>() async throws -> Service {
let name = "FooService"
guard let service = services[name] as? Service else {...}
// Prints: "returning service: Optional(Foo) as? Optional<FooService>"
print("returning service: \(service.self) as? \(Service.self)")
return service
}