Optional setters for non-optional values

When converting from Objective-C to Swift, setters that can be returned to an optional value are marked as implicitly optional, so that you can reset them to their default value easily. But why not allow an optional setter, like this?

struct FooCollection {
	subscript(index: Int) -> Element {
		get { /* code */ }
		optional set {
			// newValue is Optional<Element>
		}
	}
}

This would also allow you to easily remove values from a Dictionary with a simple extension

extension Dictionary {
	subscript(key: Key) -> Value {
		_read { /* code */ }
		optional _modify { 
			if let newValue {
				self[key] = newValue
			} else {
				self.removeElement(forKey: key)
			}
		}
	}
}

You could also use this to remove more implicitly unwrapped optionals from the language with Objective-C type conversion.

// Before:
@MainActor class UIButton: UIControl {
  // ...
  var tintColor: UIColor! { get set }
}

// After:
@MainActor class UIButton: UIControl {
  // ...
  var tintColor: UIColor { get optional set }
}
1 Like

What is supposed to be the non-optional value at the given index after the setter is invoked with nil as the new “value”?

You can already remove values from a dictionary via the key-based subscript by setting it to nil—note that if you subscript with a non-existent key, the getter returns nil.

Here again, what is supposed to be the non-optional value of tintColor after the user sets it to nil?

(Note that Swift deliberately rejects the idea of types having implicit default values, with limited exceptions (currently) when it comes to Optional.)


If your goal is that a nil value should be usable as a sentinel such that the setter can do as it pleases, this can be achieved currently using implicitly unwrapped optionals:

extension S {
  subscript(index: Int) -> Element! {
    get { ... }
    set {
      if let newValue { ... } else { ... }
    }
  }
}

Thanks to implicit unwrapping, callers can use the value returned from the getter essentially as though it's non-optional.

Usually I prefer to model this as two separate properties:

class Foo {
    var customColor: Color?
    var resolvedColor: Color { customColor ?? .white }
}

This scales well to more levels of overriding. And if needed, it is possible to distinguish between .white set explicitly and being the default one.

class Foo {
    var parent: Foo?
    var customColor: Color?
    var resolvedColor: Color { customColor ?? parent?.resolvedColor ?? .white }
}
2 Likes

I would find the opposite case more useful in my code:

subscript[key: FieldKey] -> FieldValue? {
    get {
        storageDictionary[key]
    }
    non-optional set {
        storageDictionary[key] = newValue
    }
}

so perhaps all combinations should be made available.

1 Like

Well a more general version would be a setter that can take any type of value, like this:

subscript(key: FieldKey) -> FieldValue? {
  get { storageDictionary[key] }
  set(newValue: FieldValue) { storageDictionary[key] = newValue }
}
4 Likes

Well, the non-optional value of tintColor would be the default tint color for UIButton, which is the current behavior.

Possibly related:

1 Like

Not sure how broad the applicability is of this proposed feature: is the problem big enough to warrant a new language feature?

Apart from the Obj-c interoperability (for which alternatives were proposed), where else would you use this?

I think the example I provided is relatively common, I have heard several people find it annoying that the setter has to be optional here. Not sure if this use case alone warrants a new language feature though…

1 Like

One thing you’d have to support is inout: a mutable subscript or property is supposed to have some base type where it’s valid to both get and set. That doesn’t conflict with optional setters, but does with optional getters (or arbitrary custom setter types). Of course, like with the set-only subscript proposal, we could give up on that principle and have the compiler error when there’s no “common” type that allows x = x to work (when x is inout). But that’s part of why the types aren’t fully independent, even if they could be more independent.

It won't help with subscripts, but you can accomplish this with a property wrapper for regular properties by providing a reset operation on the projection.

The biggest drawback is that you have to waste storage in the type to hold the default value, so between that and alignment padding, the struct in my example with two Doubles occupies 41 bytes :scream:

In upcoming versions of Swift, an attached macro would might work well for this, since the default value would be part of the macro's syntax transformation and encoded directly into the generated getter. That might even work for subscripts, as well.

1 Like

Property wrappers is another case where I wish for the combination of optional getter + non-optional setter.