How do I print the name of a variable in Swift?

109 views Asked by At

I often find myself typing debug prints like print("myVar: \(myVar)") and when I want to print several variables like that it becomes tedious to write.

I would like to write (or use, if it's built-in) a function that only requires me to type the name once, like printVar(myVar) and it would print myVar: 20 if myVar's value is 20.

So my questions are:

  • do you know of a built-in Swift function that already provides such functionality? I am a seasoned Swift developer and I have never heard of such thing, I have also searched the docs, Google and StackOverflow, to no avail — and no, dump() does not do the job

  • otherwise, do you know how to get the variable name in a string form?

    • Mirroring or KeyPaths are not suitable, they only address property names of a declared type, not a variable's name. I also don't want to change anything to my code architecture to be able to just add a log, so declaring a struct for that purpose is not a valid answer.

    • I have also searched for a Macro, but I only have minimal knowledge of their possibilities.


If I have that, I can even write a variadic function and then just type a concise

printVars(myVar, myOtherVar, myThirdVar)

and get a nice log, which would reaaaally be a time saver:

myVar: 20
myOtherVar: MyEnum.myCase
myThirdVar: "Arthur"
1

There are 1 answers

3
Sweeper On

If you are fine with using a macro, you can write a freestanding expression macro like this:

public struct PrintWithVariableName: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        let argExpressions = node.argumentList.map(\.expression)
        let printArgs = LabeledExprListSyntax {
            for expression in argExpressions {
                LabeledExprSyntax(
                    expression: StringLiteralExprSyntax(content: expression.trimmedDescription + ":")
                )
                LabeledExprSyntax(expression: expression)
            }
        }
        return "print(\(printArgs))"
    }

    /*
    // an alternative implementation that expands to a closure that is invoked immediately,
    // containing a print call for each expression, so that they are printed on separate lines
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        let argExpressions = node.argumentList.map(\.expression)
        let block = CodeBlockItemListSyntax {
            for expression in argExpressions {
                "print(\(literal: expression.trimmedDescription + ":"), \(expression))"
            }
        }
        return """
        {
            \(block)
        }()
        """
    }
    */
}

Add this to the providingMacros list, and declare it like this:

@freestanding(expression)
public macro printWithNames(_ args: Any...) = 
    #externalMacro(module: "...", type: "PrintWithVariableName")

Usage:

#printWithNames(x, y)
// expands to:
print("x:", x, "y:", y)