# \[Pitch\] Weak let

**URL:** <https://forums.swift.org/t/pitch-weak-let/79271>\
**Category:** Pitches\
**Created:** [April 15, 2025, 8:34am UTC](https://forums.swift.org/t/pitch-weak-let/79271 "2025-04-15T08:34:06Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [April 15, 2025, 8:34am UTC](https://forums.swift.org/t/pitch-weak-let/79271/1 "2025-04-15T08:34:06Z")

</div>

Hi! Here is a pitch based on [the discussion about weak capture in sendable closures](https://forums.swift.org/t/weak-captures-in-sendable-sending-closures/78498):

## Introduction

Currently Swift requires weak stored variables to be mutable. This restriction is rather artificial, and causes friction with sendability checking.

## Motivation

Currently swift classes with weak stored properties cannot be `Sendable`, because weak properties have to be mutable, and mutable properties are not allowed in `Sendable` classes.

Similarly, closures with `weak` captures cannot be `@Sendable`, because such captures are implicitly made mutable.

Usually developers are not aware of this implicit mutability and have no intention to modify the captured variable. Implicit mutability of weak captures is inconsistent with `unowned` or default captures.

Wrapping weak reference into a single-field struct, allows stored properties and captures to be immutable.

```swift
final class C: Sendable {}

struct WeakRef {
    weak var ref: C?
}

final class User: Sendable {
    weak let ref1: C? // error: 'weak' must be a mutable variable, because it may change at runtime
    let ref2: WeakRef // ok
}

func makeClosure() -> @Sendable () -> Void {
    let c = C()
    return { [weak c] in
        c?.foo() // error: reference to captured var 'c' in concurrently-executing code
        c = nil // nobody does this
    }
    return { [c = WeakRef(ref: c)] in 
        c.ref?.foo() // ok
    }
}

```

Existence of this workaround shows that ban on `weak let` variables is artificial, and can be lifted.

Note that resetting weak references on object destruction is different from regular variable modification. Resetting on destruction is implemented in a thread-safe manner, and can safely coexist with concurrent reads or writes. But regular writing to a variable requires exclusive access to that memory location.

## Proposed solution

Allow `weak let` declarations for local variables and stored properties.

Proposal maintains status quo regarding use of `weak` on function arguments and computed properties:

- there is no valid syntax to indicate that function argument is a weak reference;
- `weak` on computed properties is allowed, but has no effect.

Weak captures are immutable under this proposal. If mutable capture is desired, mutable variable needs to be explicitly declared and captured.

```swift
func makeClosure() -> @Sendable () -> Void {
    let c = C()
    // Closure is @Sendable
    return { [weak c] in
        c?.foo()
        c = nil // error: cannot assign to value: 'c' is an immutable capture
    }

    weak var explicitlyMutable: C? = c
    // Closure cannot be @Sendable anymore
    return {
        explicitlyMutable?.foo()
        explicitlyMutable = nil // but assigned is ok
    }
}

```

## Source compatibility

Allowing `weak let` bindings is a source-compatible change that makes previously invalid code valid.

Treating weak captures as immutable is a source-breaking change. Any code that attempts to write to the capture will stop compiling. The overall amount of such code is expected to be small.

## ABI compatibility

This is an ABI-compatible change.

## Implications on adoption

This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source or ABI compatibility.

* * *

Full proposal text - [https://github.com/nickolas-pohilets/swift-evolution/blob/mpokhylets/weak-let/proposals/NNNN-weak-let.md](https://github.com/nickolas-pohilets/swift-evolution/blob/mpokhylets/weak-let/proposals/NNNN-weak-let.md)

See also - [Thread safety of weak properties](https://forums.swift.org/t/thread-safety-of-weak-properties/422)

Note that some of the errors related to capture of mutable weak variables may not appear or have different error messages because of a bug in RBI checking. See [Let's debug: missing RBI data race diagnostics](https://forums.swift.org/t/lets-debug-missing-rbi-data-race-diagnostics/78910).

---

<div class="post-metadata">

**Author:** ![BruceMcRooster](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brucemcrooster/32/37408_2.png) [@BruceMcRooster](https://forums.swift.org/u/BruceMcRooster)\
**Post date:** [April 18, 2025, 12:45am UTC](https://forums.swift.org/t/pitch-weak-let/79271/2 "2025-04-18T00:45:06Z")

</div>

-1 from me.

At a high level, `let` indicates something doesn't change. It doesn't have a different value on two function calls, and it doesn't suddenly become `nil`. I feel like your proposal deviates from this.

> ```swift
> final class User: Sendable {
> weak let ref1: C? // error: 'weak' must be a mutable variable, because it may change at runtime
> let ref2: WeakRef // ok
> }
> 
> ```

I don't like this idea.

```swift
if user.ref1 != nil {
    print(user.ref1 != nil) // Not necessarily "true" (assuming some multithreaded context)
}

```

`let` should mean it doesn't change. It's the reason why computed properties are `var`s. This proposed ideas feels like it runs counter to every other context of `let` in Swift.

> ```swift
> func makeClosure() -> @Sendable () -> Void {
> let c = C()
> return { [weak c] in
> c?.foo() // error: reference to captured var 'c' in concurrently-executing code
> c = nil // nobody does this
> }
> return { [c = WeakRef(ref: c)] in 
> c.ref?.foo() // ok
> }
> }
> 
> ```

I think explicitly having to write `WeakRef` is in the same vein. `c` as the `let` it was originally declared as is gone. You have explicitly said "the `c` I now have is a `WeakRef`, which can change." That explicitness is a little more verbose than `weak c`, but it's clearer.  
I'm more willing to be convinced on this part, but I'm definitely opposed to `weak let`.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [April 18, 2025, 1:11am UTC](https://forums.swift.org/t/pitch-weak-let/79271/3 "2025-04-18T01:11:06Z")

</div>

`let` does not truly mean “unchanging” in Swift. In addition to the debatable case of class references, whose setters can be invoked even if they are stored in a `let`, `Atomic` values are mutable and in fact are _required_ to be declared `let`.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [April 18, 2025, 9:23am UTC](https://forums.swift.org/t/pitch-weak-let/79271/4 "2025-04-18T09:23:28Z")

</div>

Weak reference is a reference to a side table. It's a bit more complicated with ObjC weak references, but observable behaviour is the same.

So `weak let` is an immutable reference to a side table. Once assigned it will always point to the same side table. When object dies, reference to the side table does not change, changes the state of the side table. Changing state of the side table is thread safe. Changing variable to point to another side table (or even `nil`) is not.

---

<div class="post-metadata">

**Author:** ![BruceMcRooster](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brucemcrooster/32/37408_2.png) [@BruceMcRooster](https://forums.swift.org/u/BruceMcRooster)\
**Post date:** [April 18, 2025, 3:00pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/5 "2025-04-18T15:00:59Z")

</div>

I think in those two cases, `let` still maintains this property.  
Classes are understood, conceptually, to be references. Thus, my reference never changes, although the values stored in that reference may. Atomics are similar. I have explicitly written that I have an Atomic reference to some value. Of course, the underlying value could change, in the same way as classes (conceptually, not literally), but that its understood based on the type I declared, which is still immutable.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [April 18, 2025, 3:03pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/6 "2025-04-18T15:03:18Z")

</div>

Atomics are not references. [They are stored inline](https://forums.swift.org/t/second-review-se-0410-atomics/68810/6).

---

<div class="post-metadata">

**Author:** ![BruceMcRooster](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brucemcrooster/32/37408_2.png) [@BruceMcRooster](https://forums.swift.org/u/BruceMcRooster)\
**Post date:** [April 18, 2025, 3:05pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/7 "2025-04-18T15:05:30Z")

</div>

I feel like explicitly declaring the type as `WeakRef` communicates this, while `weak let` hides that explicitness. When I'm working with a `WeakRef`, it is clear that I need to check it is still valid before using it, regardless of whether it's a `let` or a `var`. `weak let` potentially becoming an invalid reference feels like mutation of what I understand to be the thing I have, IMO.

---

<div class="post-metadata">

**Author:** ![BruceMcRooster](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brucemcrooster/32/37408_2.png) [@BruceMcRooster](https://forums.swift.org/u/BruceMcRooster)\
**Post date:** [April 18, 2025, 3:22pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/8 "2025-04-18T15:22:58Z")

</div>

I'm talking about the semantics, not the actual implementations. `Atomic<T>` wraps something, so it is clear I need to abide by the semantics of that wrapper when accessing. More generally, if there is the possibility for the value to disappear/change in a `let`, that's expressed by the wrapper type.  
`weak let myVar = C()` is a non-obvious "wrapper," since there is a possibility the reference to my class can disappear, which is different from the behavior of `let myVar = C()`. I don't feel `weak let` makes this clear enough.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [April 18, 2025, 4:21pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/9 "2025-04-18T16:21:53Z")

</div>

At a semantic level, you must understand a weak reference as its own kind of value. It is bound to a specific object, but when you try to read it, you cannot always retrieve the object anymore because the object might have gone away. That changeability is inherent to weak references. Nonetheless, it is still logically the same value and will remain so forever: a weak reference bound to the original object. That is not the same as being _mutable_ because it cannot be replaced with a weak reference bound to a different object.

I understand why people are attracted to this idea that weak references are actually spontaneously mutating themselves. Mutating a class reference is a concept you’re already comfortable with, and if you think about the implementation, of course there must be some mutation there to allow the object’s memory to be reclaimed. But the idea doesn’t really work as a model of the language, among other reasons because it implies such a strange way of thinking about structs that contain weak references — you suddenly cannot ever talk about them as values again, because they are always variables that can be mutated. I have seen people go so far as to say that Swift shouldn’t allow weak references in structs because of this, all in service of trying to hold on to their mental model of spontaneous mutation.

---

<div class="post-metadata">

**Author:** ![BruceMcRooster](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brucemcrooster/32/37408_2.png) [@BruceMcRooster](https://forums.swift.org/u/BruceMcRooster)\
**Post date:** [April 18, 2025, 5:38pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/10 "2025-04-18T17:38:58Z")

</div>

I'm going to retract my -1. I believe I mentally mistook `WeakRef` as defined above with being a standard language feature. My apologies. I think the need for immutable weak references, in some form, is necessary for the language.

I will, however, point out the need for better explanations for beginners to understand weak references:

> At a semantic level, you must understand a weak reference as its own kind of value.

You say "must," but I think many beginners get by with the mutable understanding, however flawed. Until they start proposing to remove them from `struct`s, this understanding is satisfactory for resolving cyclical references and whatnot.  
Incorporating `weak let` forces the deeper understanding. I'm not opposed to forcing a more fundamental understanding, but it is one more thing for newcomers to understand that may not be necessary.  
Something like an official `WeakRef<T>` makes that difference explicit. `weak let` also makes it explicit, but doesn't alert new users as much about the potentially non-existent reference. It certainly is cleaner and more consistent with what we currently have, though.

---

<div class="post-metadata">

**Author:** ![mpangburn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mpangburn/32/45144_2.png) [@mpangburn](https://forums.swift.org/u/mpangburn)\
**Post date:** [April 18, 2025, 5:48pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/11 "2025-04-18T17:48:45Z")

</div>

> [@John\_McCall](#):
>
> It is bound to a specific object, but when you try to read it, you cannot always retrieve the object anymore because the object might have gone away.

This description resonated with me. Can I propose a pseudo-Swift translation of it to check my understanding of these semantics?

A type containing a `weak` property, e.g.

```swift
class C {}

struct Container {
    weak var c: C?
}

```

is roughly analogous to

```swift
struct Container {
    var c_storage: WeakReference<C>
    var c: C? {
        get { 
            c_storage.isDeallocated ? nil : c_storage.object
        }
        set {
            c_storage.object = newValue
        }
    }
}

```

where

```swift
// Hand-waving:
final class WeakReference<Object> {
    var object: Object // Special retain semantics; gibberish if `isDeallocated == true`
    var isDeallocated: Bool {
        goAskASideTableIfObjectIsRetainedElsewhere(object)
    }
}

```

* * *

And in the above translation, using `weak let` would be equivalent to removing the computed `set` on `var c`, i.e.

```swift
struct Container {
    weak let c: C?
}

// Becomes:
struct Container {
    var c_storage: WeakReference<C>
    var c: C? {
        c_storage.isDeallocated ? nil : c_storage.object
    }
}

```

i.e. once set, the underlying object cannot change.

Imprecision acknowledged, is this a reasonable mental model?

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [April 18, 2025, 6:42pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/12 "2025-04-18T18:42:03Z")

</div>

Yes, that's a pretty good model. I think it captures all the details relevant for this proposal.

**EDIT:** Not exactly, see important [correction](https://forums.swift.org/t/pitch-weak-let/79271/15) by @John_McCall below.

---

<div class="post-metadata">

**Author:** ![ellie20](https://avatars.discourse-cdn.com/v4/letter/e/6f9a4e/32.png) [@ellie20](https://forums.swift.org/u/ellie20)\
**Post date:** [April 18, 2025, 6:57pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/13 "2025-04-18T18:57:41Z")

</div>

Property wrappers only work for `var` and not `let`. IIRC, that's because `let` carries the connotation that the variable value itself cannot change. If `weak let` is allowed, it would make sense for `let` to be allowed for property wrappers too.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [April 18, 2025, 7:00pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/14 "2025-04-18T19:00:33Z")

</div>

I've always thought that property wrappers only work for `var`, because property wrappers make that property computed, and computed properties can only be declared with `var`. The reasoning for the latter I don't know.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [April 18, 2025, 7:01pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/15 "2025-04-18T19:01:13Z")

</div>

> [@mpangburn](#):
>
> Imprecision acknowledged, is this a reasonable mental model?

That's very close, yes! I would suggest a few very subtle refinements:

```swift
struct Container {
    // Note that this is optional: the weak property can be nil
    // because its referent has been destroyed, but it can also just
    // be nil because you stored nil into it.
    var c_storage: WeakReference<C>?
    var c: C? {
        get {
            guard let ref = c.storage else { return nil }
            return ref.isDeallocated ? nil : ref.object
        }
        set {
            if let newObject = newValue {
              // Note that we make a new reference instead of
              // modifying the existing one. `Container` still has
              // value semantics: properties of copies of the same
              // value have to be independent.
              c_storage = WeakReference(object: newObject)
            } else {
              c_storage = nil
            }
        }
    }
}

```

---

<div class="post-metadata">

**Author:** ![michelf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michelf/32/1180_2.png) [@michelf](https://forums.swift.org/u/michelf)\
**Post date:** [April 19, 2025, 12:07am UTC](https://forums.swift.org/t/pitch-weak-let/79271/16 "2025-04-19T00:07:53Z")

</div>

`weak let` might seem nonsensical at first, but I think a good part of it is that Swift conditioned us over the years into thinking it's nonsense by telling us: _"'weak' must be a mutable variable, because it may change at runtime"_.

Unfortunately we were all in the wrong and the earth is round. If new Swift doesn't mind contradicting old Swift, then it's perfectly fine.

---

<div class="post-metadata">

**Author:** ![BrentM](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/brentm/32/15134_2.png) [@BrentM](https://forums.swift.org/u/BrentM)\
**Post date:** [April 20, 2025, 7:28pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/17 "2025-04-20T19:28:49Z")

</div>

I think the problem here (As outlined by your post) is that Sendability requirements are too strict, or maybe the checks being performed by the compiler should be changed a bit.

If you have a `private weak var` the only place it can be updated is from within the class itself. Maybe we should be showing compile errors at the point of mutation, rather than the point of declaration.

For example:

if you have:

```swift
class Bar: Sendable {}

class Foo: Sendable {
      // this variable is private, AND Sendable, should we really be showing an error here?
      private weak var bar: Bar

      init(bar: Bar) {
          self.bar = bar
      }

      func updateVariable() {
           // Should we not instead be showing the error here?
           // error: updaing mutable property 'bar' on Sendable nonisolated class `Foo` can cause data races
           bar = Bar()
      }
}

```

---

<div class="post-metadata">

**Author:** ![ellie20](https://avatars.discourse-cdn.com/v4/letter/e/6f9a4e/32.png) [@ellie20](https://forums.swift.org/u/ellie20)\
**Post date:** [April 20, 2025, 10:01pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/18 "2025-04-20T22:01:05Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> I've always thought that property wrappers only work for `var`, because property wrappers make that property computed, and computed properties can only be declared with `var`. The reasoning for the latter I don't know.

Yeah, good point. And that does bring up the question of why computed properties are declared with `var` instead of `let`. A computed property without a setter or with a nonmutating setter is guaranteed to never formally mutate whatever backing storage it relies on, but it still has to be declared with `var` because (I assume) it communicates that the returned value can still vary, even though the backing storage is formally immutable. All the ways that the semantics of `weak` are being spelled out in this thread require the use of a computed property, which in my opinion shows that `weak` has the semantics of a property wrapper.

I guess `var` has a bit of a double meaning. For stored properties, it communicates the possibility of formal (exclusive) mutation. For computed properties and property wrappers, it communicates the possibility of any kind of mutation at all, including shared mutation; only the presence of a mutating setter creates the possibility of formal mutation. For example, `Atomic` has to be declared with `let`, despite the value being mutable, because the mutability is shared, not formal. But a hypothetical `Atomic` property wrapper would have to be declared as `@Atomic var a`, because the returned value can change, even though the backing storage is never formally mutated.

---

<div class="post-metadata">

**Author:** ![KeithBauerANZ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/keithbaueranz/32/27897_2.png) [@KeithBauerANZ](https://forums.swift.org/u/KeithBauerANZ)\
**Post date:** [April 21, 2025, 10:52pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/19 "2025-04-21T22:52:13Z")

</div>

+1 for me, we have to do the `Weak<T>` dance all the time to allow weak refs in Sendable classes, and it's silly.

`Weak<T>` has another issue:

```swift
protocol P: AnyObject {}

struct Weak<T: AnyObject> {
    weak var value: T?
}

let w: Weak<P>
// ^ error: 'Weak' requires that 'any P' be a class type
// - note: requirement specified as 'T' : 'AnyObject' [with T = any P]

```

This proposal would allow circumventing this (but could this restriction _also_ be lifted at the language level?)

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [April 23, 2025, 10:55pm UTC](https://forums.swift.org/t/pitch-weak-let/79271/20 "2025-04-23T22:55:22Z")

</div>

Can you talk about what source compatibility investigations you've done to qualify the weak capture change?

If we have any sort of compatibility problem here, I think there's a viable path for treating weak captures as immutable unless they're mutated within the closure. In principle, this ought to be a very simple thing to check syntactically because a weak var can only be mutated with an assignment or `inout`. However, it's possible the looseness of `&` (it's not always paired with an `inout` argument) and `mutating` protocol extensions will get in the way there. I don't know if it _needs_ to be a syntactic check anyway, though.

If we did that, we could use an upcoming feature to impose consistent mutability rules for captures. I think the right name for it would be `ImmutableWeakCaptures` or `WeakLetCaptures` rather than just `WeakLet`.

Is there some reason the proposal document lists a feature identifier? The proposal actually just flips the switch unconditionally, so there's nothing really for an upcoming feature identifier to do.

[Next page](https://forums.swift.org/t/pitch-weak-let/79271.md?page=2)
