Use Result types for specific scenarios
Context
The LLM introduced Result types into the iOS authentication service. While Result types force explicit success/failure handling, async throws is the idiomatic Swift pattern for service APIs — simpler call sites, build-time safety (forgotten error handling causes build errors), and alignment with Swift 6's typed throws direction.
Result types have their place, but not as the default for service APIs.
Decision
Default to async throws for iOS service APIs. Reserve Result types and typed throws for specific scenarios.
When to use Result types
Use Result<T, Error> for:
Cross-Boundary Operations
- Passing results across threads or async boundaries
- Storing success/failure state in memory or persistence
- Integration with completion handler-based APIs
Functional Composition
- Complex operation chaining with
map/flatMap - Promise-like patterns across different types/errors
- Complex operation chaining with
Explicit State Management
- When the result itself needs to be a first-class value
- APIs where callers need to inspect or transform errors before handling
When to use async throws
- Direct service operations (authentication, network calls, data fetching)
- Simple utility functions
- Most business logic where errors are propagated and rendered rather than handled exhaustively
When to use typed throws (Swift 6+)
- Module-internal code with stable error types
- Generic code that only propagates user-provided errors
- When you need compile-time guarantees about specific error types
Consequences
Simpler, more idiomatic Swift. Call sites use try/catch instead of switching on Result. Forgotten error handling causes build errors with throws, which is a safety net Result doesn't give you.
The trade-off is that throws allows errors to be silently propagated without explicit handling, and generic Error types lose domain-specific information (mitigated by typed throws in Swift 6).