@dynamicCallable in Swift

Hi Swifters, Today we will explore the @dynamicCallable in Swift. Lets say you have a regular Swift struct, but you want to call it like a function. Sounds weird? Well, thats exactly what @dynamicCallable lets you do! It’s like giving your types superpowers they can pretend to be functions when they are really not. Lets start with

What is @dynamicCallable?

It transforms your types into callable objects. When you mark a type with @dynamicCallable, Swift allows you to call instances of that type as if they were functions. Behind the scenes, Swift translates these calls into special method calls that you define. To make your type callable, you need to implement one or both of these special methods:

  1. dynamicallyCall(withArguments:) – For calls with positional arguments like myThing(1, 2, 3)
  2. dynamicallyCall(withKeywordArguments:) – For calls with labeled arguments like myThing(name: "John", age: 25)

Let’s dive into some real examples

Example 1: Using Positional Arguments

Let’s start witha a super simple greeter that says hello:

Before (Normal Way):

Swift
struct Greeter {
    func sayHello(to name: String) -> String {
        return "Hello, \(name)!"
    }
}

let greeter = Greeter()
let message = greeter.sayHello(to: "Alice")  // "Hello, Alice!"
print(message)

After (With @dynamicCallable):

Swift
@dynamicCallable
struct Greeter {
    func dynamicallyCall(withArguments names: [String]) -> String {
        if names.isEmpty {
            return "Hello, World!"
        }
        return "Hello, \(names[0])!"
    }
}

let greeter = Greeter()
let message1 = greeter("Alice")     // "Hello, Alice!"
let message2 = greeter()            // "Hello, World!"
print(message1)
print(message2)

What happened? We added @dynamicCallable to our struct, then we wrote a special method dynamicallyCall(withArguments:), and now we can call greeter("Alice") instead of greeter.sayHello(to: "Alice"). Nice right?

Example 2: Using Labeled Arguments

Now let’s try the second type of dynamic call with labels:

Swift
@dynamicCallable
struct PersonCreator {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, String>) -> String {
        var person = "Person: "
        
        for (key, value) in args {
            person += "\(key) is \(value), "
        }
        
        return String(person.dropLast(2))  // Remove last ", "
    }
}

let creator = PersonCreator()
let person1 = creator(name: "John")
let person2 = creator(name: "Alice", age: "25")
let person3 = creator(name: "Bob", age: "30", city: "NYC")

print(person1)  // "Person: name is John"
print(person2)  // "Person: name is Alice, age is 25"  
print(person3)  // "Person: name is Bob, age is 30, city is NYC"

What’s different here? We used withKeywordArguments instead of withArguments and now we can call it with labels: creator(name: "John", age: "25"). Each argument has a name and a value now.

A Real-World Example: A Simple Logger

Here is something you might actually use in a real app:

Swift
@dynamicCallable
struct Logger {
    func dynamicallyCall(withArguments messages: [String]) -> Void {
        let timestamp = Date()
        let fullMessage = messages.joined(separator: " ")
        print("[\(timestamp)] \(fullMessage)")
    }
    
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, String>) -> Void {
        let timestamp = Date()
        var logParts: [String] = []
        
        for (key, value) in args {
            logParts.append("\(key): \(value)")
        }
        
        print("[\(timestamp)] \(logParts.joined(separator: ", "))")
    }
}

let logger = Logger()

// Simple logging
logger("App started")
logger("User logged in", "successfully")

// Structured logging
logger(event: "user_login", user: "john123", status: "success")

// Output:

// [2025-06-06 18:45:38 +0000] App started
// [2025-06-06 18:45:38 +0000] User logged in successfully
// [2025-06-06 18:45:38 +0000] event: user_login, user: john123, status: success

When Should You Use @dynamicCallable?

Great for:

  • Making APIs feel more natural
  • When you want function-like syntax
  • Building configuration systems
  • Creating domain-specific languages (DSLs)

Not great for:

  • Simple, straightforward operations (regular methods are clearer)
  • When you need strict type checking at compile time
  • When code readability is more important than cool syntax

@dynamicCallable is like giving your Swift types a new way to be called. It’s not magic, it’s just a cleaner syntax for method calls. The key is to use it when it makes your code more readable and natural, not just because it looks cool.


Posted

in