Swift
closure
programming
generic
duplicates

is it possible to create a generic closure in Swift?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Swift closures themselves cannot be generic — you cannot write let closure = { <T>(value: T) -> T in ... }. However, you can achieve generic closure behavior by defining the closure as a property or parameter inside a generic function, a generic type, or by using a protocol with an associated type. The key insight is that generics in Swift are resolved at the declaration site (function or type), not at the closure expression level.

Why Closures Cannot Be Directly Generic

In Swift, generic type parameters are declared on functions, types, and protocols — not on closures:

swift
1// This is NOT valid Swift:
2let identity = { <T>(x: T) -> T in return x }  // Compile error
3
4// Generic type parameters must be on a function or type declaration
5func identity<T>(_ x: T) -> T { return x }     // Valid

A closure is an expression, not a declaration. Swift's grammar does not allow generic parameter lists on closure expressions.

Solution 1: Generic Function That Returns a Closure

Wrap the closure in a generic function. The generic parameter is resolved when the function is called:

swift
1func makeTransformer<T>(_ transform: @escaping (T) -> T) -> (T) -> T {
2    return transform
3}
4
5let doubleInt = makeTransformer { (x: Int) in x * 2 }
6let uppercaseString = makeTransformer { (s: String) in s.uppercased() }
7
8print(doubleInt(5))            // 10
9print(uppercaseString("hello")) // "HELLO"

Each call to makeTransformer specializes T to a concrete type. The returned closure is not generic — it is a specific (Int) -> Int or (String) -> String.

Solution 2: Generic Function with Closure Parameter

The most common pattern — define a generic function that takes a closure:

swift
1func apply<T, U>(_ value: T, transform: (T) -> U) -> U {
2    return transform(value)
3}
4
5let result1 = apply(42) { String($0) }       // "42" (Int -> String)
6let result2 = apply("hello") { $0.count }    // 5 (String -> Int)
7let result3 = apply([1,2,3]) { $0.reversed() } // [3, 2, 1]

The closure's types are inferred from the generic context of the function call.

Solution 3: Closure Property on a Generic Type

swift
1struct Transformer<T> {
2    let transform: (T) -> T
3
4    func apply(_ value: T) -> T {
5        return transform(value)
6    }
7}
8
9let intTransformer = Transformer<Int> { $0 * 2 }
10let stringTransformer = Transformer<String> { $0.uppercased() }
11
12print(intTransformer.apply(5))          // 10
13print(stringTransformer.apply("hello")) // "HELLO"

The closure type is bound when you create the Transformer with a specific type parameter.

Solution 4: Protocol with Associated Type

For truly polymorphic behavior where one object handles multiple types:

swift
1protocol Processable {
2    associatedtype Input
3    associatedtype Output
4    func process(_ input: Input) -> Output
5}
6
7struct AnyProcessor<I, O>: Processable {
8    typealias Input = I
9    typealias Output = O
10    private let _process: (I) -> O
11
12    init(_ closure: @escaping (I) -> O) {
13        _process = closure
14    }
15
16    func process(_ input: I) -> O {
17        return _process(input)
18    }
19}
20
21let stringToInt = AnyProcessor<String, Int> { $0.count }
22let intToString = AnyProcessor<Int, String> { "Number: \($0)" }
23
24print(stringToInt.process("hello"))  // 5
25print(intToString.process(42))       // "Number: 42"

Solution 5: Using Any and Type Erasure

For cases where you need a single closure that handles multiple types (less type-safe):

swift
1// Type-erased closure — loses compile-time type safety
2let genericClosure: (Any) -> String = { value in
3    return String(describing: value)
4}
5
6print(genericClosure(42))       // "42"
7print(genericClosure("hello"))  // "hello"
8print(genericClosure([1,2,3]))  // "[1, 2, 3]"

This works but bypasses the type system. Prefer generic functions or types when possible.

Solution 6: Closures with Generic Constraints

swift
1func makeComparator<T: Comparable>() -> (T, T) -> Bool {
2    return { $0 < $1 }
3}
4
5let intCompare: (Int, Int) -> Bool = makeComparator()
6let stringCompare: (String, String) -> Bool = makeComparator()
7
8print(intCompare(3, 5))            // true
9print(stringCompare("a", "b"))     // true

The generic constraint T: Comparable ensures the closure only works with types that support comparison.

Storing Generic Closures in Collections

swift
1// You cannot store closures of different generic types in the same array:
2// let closures: [(T) -> T] = [...]  // T is not defined
3
4// Use type erasure with a protocol:
5protocol Transform {
6    func apply(to value: Any) -> Any
7}
8
9struct TypedTransform<T>: Transform {
10    let closure: (T) -> T
11
12    func apply(to value: Any) -> Any {
13        guard let typed = value as? T else { return value }
14        return closure(typed)
15    }
16}
17
18let transforms: [Transform] = [
19    TypedTransform<Int> { $0 * 2 },
20    TypedTransform<String> { $0.uppercased() }
21]
22
23print(transforms[0].apply(to: 5))        // 10
24print(transforms[1].apply(to: "hello"))   // HELLO

Common Pitfalls

  • Trying to add <T> to a closure expression: Swift's syntax does not support generic parameters on closure literals. The generic parameter must come from the enclosing function or type — not from the closure itself.
  • Confusing generic closures with Any parameters: Using (Any) -> Any instead of generics compiles but loses type safety. The compiler cannot catch type mismatches, and you need runtime casts with as? everywhere.
  • Expecting type inference across stored closures: When storing a closure in a property, Swift needs to know the concrete type. let f: (T) -> T is invalid unless T is defined by the enclosing generic type.
  • Forgetting @escaping for stored closures: Closures stored in properties or passed to asynchronous functions must be marked @escaping. Omitting it causes a compile error when assigning the closure to a property.
  • Over-engineering with protocols and type erasure: For simple cases, a generic function that takes a closure parameter is sufficient. Adding protocols, associated types, and type-erased wrappers adds complexity that is rarely needed.

Summary

  • Swift closures cannot have their own generic type parameters — generics must come from the enclosing function or type
  • The most common pattern is a generic function that takes a closure parameter
  • Generic types can store closures whose types are bound to the type parameter
  • Use protocols with associated types for polymorphic behavior across multiple types
  • Avoid Any for generic-like behavior — it loses compile-time type safety
  • @escaping is required when closures are stored in properties or used asynchronously

Course illustration
Course illustration

All Rights Reserved.