Daniel Tull: Today I Learned

Using KeyPaths in a Result Builder

Monday, 19 February 2024

Every now and then, I’ve attempted to use a KeyPath in result builders for fun and sure profit… except I’ve never managed to get short-form key path expression working.

For example, suppose we have the following simple KeyPathBuilder1 and usage:

@resultBuilder
enum KeyPathBuilder<Root> {

  static func buildBlock<A>(
    _ keyPath: KeyPath<Root, A>
  ) -> KeyPath<Root, A> {
    keyPath
  }
}

struct User {
  let name: String
}

@KeyPathBuilder<User>
var keyPath: KeyPath<User, String> {
  \.name
}

When we try to use the builder with a short-form key path, we are presented the following error for the line with \.name:

Cannot infer key path type from context; consider explicitly specifying a root type

Swift is informing us that inference is not possible right now, so we need to add the root type to the key path:

@KeyPathBuilder<User>
var keyPath: KeyPath<User, String> {
  \User.name
}

While the full form key path is indeed very explicit, it is also wordy when one considers that we probably want to do something more interesting with a builder and we have many key paths listed one after another.

Fixing the error with buildExpression

I stumbled over a solution today, and I’m not entirely sure why I even tried it if I’m honest.

static func buildExpression<A>(
  _ expression: KeyPath<Root, A>
) -> KeyPath<Root, A> {
  expression
}

Adding the buildExpression function to KeyPathBuilder will allow us to use the short-form key path expression as we originally desired! 🥳

An example project can be found on GitHub.

  1. And this really is too simple for real use, but it’s the minimum viable result builder that shows the issue.