SE-0479: Method and Initializer Key Paths

Hello Swift community,

The review of "Method and Initializer Key Paths" begins now and runs through May 5, 2025. The proposal is available here:

swift-evolution/proposals/0479-method-and-initializer-keypaths.md at main · swiftlang/swift-evolution · GitHub

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager by email or Swift Forums DM. When contacting the review manager directly, please include "SE-0479" in the subject line.

Trying it out

If you'd like to try this proposal out, you can download a toolchain supporting it here. Any trunk development snapshot dated March 25, 2025 or later should do. You will need to enable the experimental feature KeyPathWithMethodMembers using SwiftSetting.enableExperimentalFeature in a Swift package manifest or the -enable-experimental-feature compiler flag in other build systems.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Happy reviewing,

—Becca Royal-Gordon
SE-0479 Review Manager

16 Likes

I think this is an important feature, and I'm looking forward to it. I'm a little scared by how light the proposal is; I don't feel like I know enough about KeyPaths to know whether there are new cases of actor isolation or sendability that need to be stated, or whether the existing KeyPath rules are sufficient.

I think the example about @dynamicMemberLookup should be clear that we've lost argument labels, and aren't gaining transparent method forwarding:

struct S {
    func doStuff(withThings: Int, etcetera: String) {
        // ...
    }
}

@dynamicMemberLookup
struct T {
    var s: S
    subscript(dynamicMember keyPath: KeyPath<S, T>) -> T {
        s[keyPath: keyPath]
    }
}

// ...

let t = T(s: S())
t.doStuff(withThings: 3, etcetera: "hi") // some kind of error about incorrect arg labels
t.doStuff(3, "hi") // accepted, disappointingly

I think the proposal should be clear about whether a mutating method requires a WritableKeyPath to be callable (I think yes?), whether a KeyPath to a consuming method can be formed (I think no), etc.

4 Likes

Aww, I didn't realize we fully lost labels. Perhaps we can get them back when closure labels are added back (SE-0111).

3 Likes

The syntax for unapplied subscripts appears to be missing.

@dynamicMemberLookup

Discarding of argument labels makes this really not work with @dynamicMemberLookup as Swift APIs quite often differentiate just by argument labels, making those APIs ambiguous to call.
It would make it a source breaking change to introduce new method overloads that are just disambiguated by argument labels, essentially on any Type. It also would fail to disambiguate method and properties, making just this proposal source breaking even without further changes.
In fact, the @dynamicMemberLookup integration actually has the same problems as if we would allow KeyPath method references without argument labels as was discussed previously.

Even the example in the proposal shouldn't compile because subtract is ambiguous:

struct Calculator {
  var subtract: (Int, Int) -> Int { return { $0 + $1 } }
  func subtract(this: Int) -> Int { this + this}
  func subtract(that: Int) -> Int { that + that }
}

let dynamicCalculator = DynamicKeyPathWrapper(root: Calculator())
let subtract = dynamicCalculator.subtract // <- ambiguous, could be the property or one of the two methods.
print(subtract(10))

I think this either needs to support argument names (which I would really like but sounds rather difficult) or remove this integration.

Edit: I have tried it out with the latest nightly compiler.. This example seems to resolve to the property subtract. It still fails to compile because the call to subtract now expects two arguments because the subtract property returns a closure with two arguments. This should at least make this non-source breaking for existing code.
However, as soon as users start using this new feature, it would still introduce ambiguities if a methods with the same base name but different argument labels are introduced. That effectively would make it impossible to add new method overloads based on argument names to any type without risking to introduce source breaking changes for users that use @dynamicMemberLoop with method key paths. That is very undesirable.

mutating methods

I'm also quite concerned that mutating methods aren't supported, nor mentioned in the futures directions (there isn't even Future Directions section?). I can't really imagine how we could support mutating methods in the future without a very different strategy.

This also seems in direct conflict with the motivation section:

Key path methods and initializers will also enjoy all of the benefits offered by existing key path component kinds, e.g. simplify code by abstracting away details of how these properties/subscripts/methods are modified/accessed/invoked

Emphasis mine.

IMHO "value semantics" is one of greatest Swift feature. It would be quite unfortunate if we wouldn't support value types. As proposed this can only abstract modifications for types that have reference semantics.

consuming methods

It is a bit unclear how deeply consuming methods are actually supported. Does this actually support consuming methods without copying self or does this just convert it to a non consuming method and adds a copy before calling it? Concretely, would this trigger Copy on Write (CoW) or not?:

extension Array 
    consuming func appending(_ element: Element) -> Self {
        self.append(element)
        return self
    }
}
let array = [0, 1, 2]
// is CoW triggered here?
let newArray = array[keyPath: \.appending(3)]

I think it does need to do a copy as consuming get accessors aren't yet supported.
Is this something that could be added later? Any other way to not trigger CoW?


With that being said, I very much think this is a problem worth solving.
However, without a future direction nor any examples in the motivation section I don't really see a use case where I could use method key paths as proposed. It doesn't work for my use cases if it doesn't support mutating methods nor argument names with @dynamicMemberLoopkup.

13 Likes

I would expect a "future directions" mention of some possible solution to restore key paths for throwing functions.

Defining a function with throws is 1:1 functionally equivalent to have it return Result instead, but a function returning Result would actually be supported by this proposal. This encourages the use of Result instead of throws, which is of course a bad direction for the language.

The proposal is designed to be rather minimal and focused on essential functionality, but the fact that it doesn't support many current and future language patterns makes its applicability very limited, so I would expect, in general, a "future directions" sections that addresses the limitations with some options to restore argument labels, effectful functions, mutating et cetera.

6 Likes

That for me would be a giant -1, please send it for review when labels are sorted out.

I am still quite sour for SE-0111 and losing labels for stored closures. I was -1 on that proposal because I did not agree it was a “temporary thing” and mandatory argument labels was a key statement for the language and something I miss in all other ones I have used (Obj-C aside of course ;)).

I am kind of losing hope we will ever get them back and this proposals does another step in the wrong direction as far as they are concerned. I read the proposal this morning and I got the opposite impression, that argument labels and labels distinguished overloads were supported so I am a bit shocked reading the discussion… then again you are a LOT closer to Swift experts than I am so that is not impossible to understand… still, the proposal needs to be clearer.

18 Likes

I’m very much excited about the possibilities for using keypaths to invoke method and initialisers, and overall I think the proposed syntax fits well within the language.

However, like others have mentioned, I’m disappointed about the restrictions regarding mutating, throws, and async, which significantly limits the utility of this feature.

It would be good if these ideas could at least be addressed in a Future Directions section, so we can be sure the proposed syntax leaves open a viable path forward.

1 Like

To me the usage example should be written like this

// Key paths to Calculator methods
let squareKeyPath = \Calculator.square(of:)
let cubeKeyPath = \Calculator.cube(of:)

so the argument labels are clearly spelled out, as an integral part of the method name.

The fact that (of:) can be omitted is an extra convenience, but it should not be considered the "canon" way of referring to unapplied methods, so I would expect (of:) to be spelled out in proposals and documentation.

7 Likes

-1 from me.

Lack of support for argument labels, mutating and effectful functions makes me conclude that key paths are a wrong tool for the job, at least for the @dynamicMemberLookup.

I would appreciate proposal text summarising advantages over unapplied, partially applied and applied methods. Going through entire pitch thread complicates review process.

Method keypaths are Hashable, which is indeed an improvement compared to method types.

Pitch mentions that keypaths have metadata, but aside from Hashable conformance there is no public API to use this metadata. (There is SPI _kvcKeyPathString available only for ObjC properties).

Handling properties and methods in a unified manner can be beneficial for some use cases, but can be limiting for use cases that actually want to do something with the arguments.

Using method keypaths with @dynamicMemberLookup does not allow to inspect or modify values of arguments and the result.

IIUC, in this example:

@dynamicMemberLookup
struct DynamicKeyPathWrapper<Root> {
    var root: Root

    subscript<Member>(dynamicMember keyPath: KeyPath<Root, Member>) -> Member {
        root[keyPath: keyPath]
    }
}

let dynamicCalculator = DynamicKeyPathWrapper(root: Calculator())
_ = dynamicCalculator.subtract(10)

Member is (Int) -> Int, so the value 10 is not even stored inside the keypath.

Unless I'm missing something, I don't see how this feature would enable building Mockable<T> or TransitionParticipant from the usage examples given in the pitch.

5 Likes

Big -1 from me in its current form as well.
I agree with all the commentary above, especially the lack of argument labels.
I am also still quite bummed that closure argument labels haven't returned so far but that's another topic.

5 Likes

What is your evaluation of the proposal?

-1 overall to the proposal as it stands. Overall, I feel adding the extra surface area to KeyPaths isn’t warranted if they can only provide a half-baked feature.

Is the problem being addressed significant enough to warrant a change to Swift?

The motivation as stated is seemingly to achieve syntax symmetry with properties, and offer a Hashable conformance to named synchronous nonthrowing nonmutating function types. Hashable conformance for functions seems a decent enough goal, but this doesn’t feel a generally useful approach due the many restrictions. The syntax symmetry seems of academic interest at most, if you’d then need to use unapplied function syntax directly to make a usable/useful API out of it.

Does this proposal fit well with the feel and direction of Swift?

While it fits with the direction of KeyPaths, but KeyPaths don’t really fit the direction of the language as Swift stands today due to the many limitations around async/mutating/throws effects. This feels more like documentation for a proof-of-concept implementation, rather than a vision of how the language should evolve. I’d rather see a vision for KeyPaths in a broader context, to support effects and argument labels, before this is introduced, to avoid locking in limiting decisions.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No other languages to draw on here, I’ve been all-in on Swift for a while.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read through the proposal a few times to come up with my concerns.

In addition to queries others have raised, the proposal should clarify the ability to create keypaths with arguments included - examples only use Int arguments, which is Hashable. Can non-Hashable arguments be included, and does this extend subscript keypaths with non-Hashable arguments too? The Hashable limitation is acceptable for subscripts, but cripples the usefulness for general methods.

3 Likes
  • What is your evaluation of the proposal?

-1. As others have said, the proposed improvement in Swift language
utility is very limited. It is unclear how the shortcomings of the
proposal will/can be made up for in the future—the lack of a Future
Directions sections is notable.

A previous
proposal
on virtually the same topic is
more thorough.

It was unclear to me whether or not this proposal would actually
foreclose the possibility of method KeyPaths with argument labels.

  • Is the problem being addressed significant enough to warrant a change to Swift?

To express proxies in Swift, today, one has to write a bunch of
boilerplate code. I believe that is a significant problem. If the
language allowed closures with argument labels, and if it provided
method KeyPaths that could be used with dynamic member lookup, then it
would be easier to create useful proxies than it is today.

The current proposal does not address an existing problem in a
significant way. As others have said, it proposes taking a step that is
too small to perceive an improvement.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal, reviewed SE-0111, and read the previous method
KeyPaths proposal that I mention above.

David

3 Likes

I am a fan of this direction, as I have long felt that KeyPaths should support methods.

I do agree that it is too bad that async and throws remain still unsupported, but that does not change my view of the proposal because this proposal won't foreclose that possibility. Property key paths already do not support async and throws, so this isn't something that's "newly missing".

I agree that the lack of closure argument labels for @dynamicMemberLookup is very unfortunate. I think some investigation into the potential source-compatibility issues should be done.

Nevertheless, I would still welcome this change because it would unlock some important capabilities, even despite the existing limitations. Some elaboration on the Future Directions would be really welcome, though.

Agree with both statements.

Thank you for this. I wasn’t aware of this proposal, and while it’s not perfect (and also pre-dates concurrency), it is much more detailed than this one.

Thank you everyone for your feedback. I'm looking into what it take to support argument labels, but wanted to address some of the notes on the proposal so far.

Regarding effectful types not supported by this feature: These will require new KeyPath types and so have been left out of this proposed feature. Additionally, the lack of support for effectful types also affects other key path component kinds and can be addressed in a unified proposal that tackles this issue for all key path component kinds, including what we're introducing here. I will add this to the future direction section to the proposal text as well.

I have also revised the motivation section to better reflect the limitations of this proposed feature. The original text was carried over from the pitch, written under the earlier assumption that full parity between instance methods and key path methods would be feasible before I discovered the current limitations of the compiler and KeyPath model.

6 Likes

Thanks for the update! The future directions section is still very light though.

Sorry for nit picking but this is also not entirely correct as "normal" KeyPaths already support one "effect", namely mutations on value types through WritableKeyPath which Method Key Paths do not. It would be great if it could be explained why this doesn't work with Method Key Paths and why we are confident that this can be added incrementally.

4 Likes

WritableKeyPath supports a mutating setter, but you can't form a key path to a property with a mutating getter (for instance, a lazy property in a struct). Since this proposal basically treats methods like read-only properties, mutating methods are equivalent to read-only properties with mutating getters, so it makes sense that this wouldn't be supported.

(As review manager, I'm not going to take a side on whether the lack of mutating method support is tolerable or whether it means we should look for a different approach; I'm just pointing out that Amritpan is correct when she says that the key path runtime is not currently equipped to support mutating methods.)

5 Likes

Hello Swift community,

The authors have made a minor amendment to the proposal text. This amendment:

  • Modifies declaration names in one of the code examples so it no longer appears to involve unrelated declarations in another example.
  • Expands discussion of the limitations on mutating, throws, async, etc. and suggests that they might be addressed as a future direction.

Since there's still plenty of review left and the amendment doesn't actually change the design, the review will not be extended; it will end on May 5, 2025 as originally scheduled.

—Becca Royal-Gordon
SE-0479 Review Manager

6 Likes