SE-0507: Borrow and Mutate Accessors

Hi everyone,

The review of SE-0507 "Borrow and Mutate Accessors" begins now and runs through February 9, 2026.

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 via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

Try it out

Development toolchains built from main have this feature available as an experimental feature with the name BorrowAndMutateAccessors.

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 https://github.com/swiftlang/swift-evolution/blob/main/process.md .

Thanks for contributing to Swift!

Doug Gregor
Review Manager

14 Likes

For novices coming to Swift, the set of possible accessors may be confusing. In order of increasing skill level:

  • When do I use get vs. borrow vs. yielding borrow?
  • Why can't the compiler decide for me (at least in some subset of cases)?
  • Since the compiler knows how to synthesize other accessors to satisfy protocol requirements, could it infer borrow instead of get when it is safe to do so (possibly only when Library Evolution is disabled) and when it is expected to improve performance?
  • Is the second function call required by the coroutine-based yielding borrow really so expensive that between it and get we need a third option?
    • Do we have performance metrics showing the problem?
    • If the yielding accessors are so much more expensive, should we consider warning on use when the compiler can see that a non-yielding accessor would be valid?

Regarding the source compatibility concern, can the compiler emit a diagnostic if it detects this ambiguity?

7 Likes

The limitation of yielding accessors is not the performance cost. It’s that the need for the cleanup piece to run ends the lifetime of the yielded Span and that severely limits what you can do with it.

The “getSpan” example in the proposal shows one of these scoping problems we would have to wrangle with if we only had yielding borrow and mutate.

(There is a real performance cost, but that’s less of a dealbreaker for most cases¹ than the semantic limits that only having yielding accessors would imply).


¹ That said, for some use cases, “anything more than a bounds check and a C pointer dereference” is unacceptable, so any added performance overhead does matter.

3 Likes

Yes, please and thank you. I'm also looking forward to the borrowing return futures direction (although I imagine it's a long ways off).

2 Likes
  • What is your evaluation of the proposal

+1

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

Yes, the vision document clearly states that borrow and mutate are needed as safe alternative to unsafe pointer api

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

Yes safe api alternatives

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

Swift is the only language I know that is adding move semantics. This seems like a huge undertaking. It’s great to see the commitment to the evolution of the language

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

Read the proposal and vision document.

some questions:

  • Does this proposal close most if not all the vision ideas? If not, what are the mayor pieces missing to close on the vision?
  • Could we have an updated table in the vision or proposal with how borrow and mutate compose with others?
  • Is there a way to document which changes non ABI breaking ? ( seems like there are many axis so note sure if this can be written down in proposal form )
1 Like

some questions after reading and playing around with the initial implementation:


currently it appears that this feature only works with structs – is that an inherent limitation? i know that reference types are explicitly called out as being unsupported, but are enumerations expected to be compatible with the feature in some manner? if not, maybe that should be stated explicitly.

perhaps relatedly, it seems that forwarding through to an optional of a non-bitwisecopyable type doesn't work:

struct Source {
    var _s: String? = ""

    var s: String? {
        borrow { _s }
    }
}

struct Wrapper {
    var i: Source?

    var prop: String? {
        borrow { i?.s } // 🛑 – but this works if it's e.g. `Int?`
    }
}

what is the restriction at play here? temporary values in the optional chain?


Note that the value being returned must be a stored value that will outlive the execution of the accessor. It is illegal to return a local or temporary value

this wording in the proposal made me think the following could work because the storage lifetimes seem like they meet the constraint, though neither do:

var global = 0

struct S {
  var p: Int {
    borrow { global } // âś…
    mutate { &global } // 🛑
  }
}

do {
  var i = 0
  var local: Int {
    borrow { i } // 🛑
  }
}

the accessors vision doc on the other hand makes it sound like none of these should work including the borrow returning the global.


it seems reference storage types are also currently unsupported – is that an inherent limitation (perhaps the 'can't return a temporary value' one?)?

struct Ref {
    unowned var _obj: AnyObject

    unowned var val: AnyObject {
        borrow { _obj } // 🛑
    }
}

i had a reflexive sense of distaste that both mutating borrow and nonmutating mutate are expected to be valid. do i understand correctly that the '-ing form' specifies the semantics that apply to the container during the property access rather than the value, so should be interpreted as something like nonmutating(self) mutate(value) var ...?

i saw in the pitch thread some discussion about the meaning of borrowing borrow. is a borrowing mutate a valid combination (it does not appear to compile at the moment)?

5 Likes

I chewed on this for a long time and wondered when I would ever not want a borrow accessor to be the default behavior (at least for anything more complex than simple POD types that fit in one or two registers), given that it's generally more performant. But then I talked myself into the argument that if I'm writing a computed property, chances are more likely that I'm returning a constructed value or something retrieved from a place that isn't already borrowed. The power here is in being able to write things like wrapper types and other low-level abstractions that just pass through the address without copying (unless the caller needs to).

The limitation that borrow must be paired with mutate instead of yielding mutate feels a bit too restrictive. I can imagine situations where the reading side of an access can be expressed simply as "borrow some existing value" but where the mutation is "mutate some existing value, which can be done by address, but follow that up by post-work to adjust some other internal state". The latter requires yielding mutate instead of mutate, but that forces me to take the overhead of a coroutine to make the read side yielding borrow as well. Breaking that restriction also seems like it would align with the way accessors are synthesized for stored properties today: if I write struct S { var x: String }, the compiler generates a get, a set, and a _modify (the old yielding mutate), but not a _read.

Speaking of stored properties, how do they fit into this picture? The compiler synthesizes get/set/_modify for those today, but what if I want to be able to pass a stored property directly into a borrowing parameter context without incurring any copies—will that just work? (Maybe it does today already?)

The proposal also mentions that borrow accessors can return literals, which makes sense as they have immortal lifetimes. It would be great if we could make this the default behavior for shorthand read-only computed properties where all code paths return a literal. When I write this:

var name: String { "SomeName" }

What I really want is this, but I don't want to have to write the borrow out every time:

var name: String { borrow { "SomeName" } }

(I tried this in whatever the current nightly build is on Godbolt and got a compiler crash in the SIL verifier, I assume that's just a bug in the early implementation? link)

The limitation that borrow/mutate can't borrow from unsafe pointers is also understandable, but unfortunate. Maybe Spans are a better fit here, but can't we just acknowledge that it's unsafe with features we have today? The Future Directions given this example:

Supporting cases like this will require some way to annotate the return expression. For example, we might provide a function-like marker:

var first: Element {
  borrow {
    return unsafeResultDependsOnSelf(_storage.pointee)
  }
}

Could we just express that as this instead:

var first: Element {
  borrow {
    return unsafe _storage.pointee
  }
}

Or is the "depends on self" part load-bearing enough that it can't be expressed with just the unsafe keyword?

5 Likes

Would didSet suffice, do you think?

Since willSet/didSet aren't supported for computed properties, I'm not sure I'm following the suggestion. Do you mean that if didSet support was added, then I could express such a property as { borrow {} mutate {} didSet {} }? That might work in some cases but if there's any state I wanted to track between mutate and didSet (which would one obvious case for using a coroutine mutator), that becomes difficult.

I think carving out an exception to allow didSet on a computed property would be strictly worse than just letting me mix borrow and yielding mutate. That combination is much clearer about the control flow I want to express in each direction.

4 Likes

Yes, sorry I was unclear!

I'm struggling to see how a nonmutating mutate accessor would be useful, except for an unsafe API, because it can't enforce exclusivity. It's nonmutating, so it can't enforce exclusivity statically. It's not yielding, so it can't enforce exclusivity dynamically.

Since let properties of classes (and let globals) don't require dynamic exclusivity checks, would it be possible to use borrowing accessors for those?

2 Likes

Also, as terminology, nonmutating mutate is confusing to the point of absurdism. Anyone who sees that and hasn’t read this proposal document is going to think they’re being pranked.

21 Likes

Just speaking for myself, I know several places where nonmutating mutate can be useful.

For instance, we can already build a property wrapper for a reference type:

class Reference {
    var a = 0
    init(_ value: Int) {
        a = value    
    }
}

@propertyWrapper
struct Wrapper {
    private let ref: Reference
    init(wrappedValue: Int) {
        ref = Reference(wrappedValue)
    }

    var wrappedValue: Int {
        get {
            ref.a
        }
        nonmutating set {
            ref.a = newValue
        }
    }
}

It's pretty straightforward for me to transfer my mental model of nonmutating set to nonmutating mutate .

1 Like

I wanted to address some of the more straightforward points related to the current implementation sooner as I continue to work through the feedback.

@allevato Borrow accessors cannot “borrow” literals in a general sense. I have updated the example diagnostic in the proposal which suggested otherwise and merged a fix to correctly diagnose.

@jamieQ “Trivial types” and “Non trivial types” should be diagnosed uniformly, I am working on a fix. But yes, the diagnostics you see for optional chaining and unowned are because we cannot return a “borrowed result” due to requiring a temporary, cleanup, etc.

2 Likes

The reference-type example would require nonmutating yielding mutate, not nonmutating mutate, because mutable stored properties of class instances use dynamic exclusivity enforcement. (Exclusivity enforcement is explained in SE-0176.)

I would love to see a separator allowed in the protocol definition. (This request is orthogonal to this proposal itself, but it might be a good vehicle for it :slight_smile: )

For example:

// Current grammar:
var element: Element { yielding borrow yielding mutate } 

// Can we make this allowed too?
var element: Element { yielding borrow; yielding mutate } 
4 Likes

What would be the suggested adoption for macros that deal with properties? e.g. Observation synthetically generates accessors, should that be considered for the macro to generate the modify and borrow cases? Can it determine enough information to know if those are useful (i.e. does it need the type to determine if that type is potentially non-copyable? etc.)

@allevato Great points.

borrow/set cannot be paired for ~Copyable and conditionally Copyable types. As you said, borrow/yielding mutate can be useful in some cases. This restriction can be lifted in the future.

borrowing parameter modifier restricts copies only in the callee. For Copyable types, there can be copies at the callsite.

When StrictMemorySafety is enabled, I think it should be possible to use unsafe to express return expressions in borrow accessors that rely on unsafe pointers. However, this is deferred to future work as well.

Yes, the ownership modifiers refers to the ownership semantics of self.

borrowing mutate should be invalid.

1 Like

nonmutating mutate will be useful for unsafe pointer based containers.

Quoting @tbkka from the pitch review:

If I were to look for a compelling use case, I'd tinker with things like out-of-band debugging/logging hooks, hardware register access, or in-memory database wrappers. There are likely use cases similar to MutableSpan, for which you want to be able to "mutate" through the value without having a mutable copy of the value itself.

2 Likes