Admittedly, I hadn't considered optionals before. But yeah, optional borrow/inout references are a particularly compelling case, particularly for a noncopyable dictionary type. Optionals are convenient because of their status as a fundamental currency type with lots of syntactic sugar; it'd be unfortunate to lose that convenience the moment one needs a different kind of ownership.
However, I think Optional<Borrow<T>> (and Optional<Inout<T>>) would still be inconvenient, contrary to the general design goals of optionals. The main issue being that Optional<Borrow<T>> would have a very different interface from borrowing Optional<T>, even though they're just different representations of the same high-level value.
For example, if we get borrowing/mutating optional bindings, like if borrow/if inout, it'd be nice for optional borrows to use the same syntax as borrowed optionals. But with Optional<Borrow<T>>, one would use a normal consuming optional binding, with the wrapped value behind a layer of indirection. Pattern matching would have a similar issue, with the extra difficulty of pattern matching through a computed property.
These inconsistencies could hurt usability, and could make it more difficult to switch between different representations during refactoring.
I think it would be better to have dedicated OptionalBorrow and OptionalInout types instead. The main benefit is that OptionalBorrow<T> would have a consistent interface with borrowing Optional<T>. It could have the same syntactic sugar, including optional binding and optional chaining. It could have type sugar, such as (borrowing T)?.
Ideally, it would be easy to convert between different representations, such as converting a borrowed optional to an optional borrow, converting an optional inout to an optional borrow, or (for copyable types) converting an optional borrow to an owned optional. In Rust, the Option type has different helper methods for this purpose, such as as_ref, as_mut, as_deref, and cloned. The helper methods are better than explicit pattern matching, but I think they still create an undesirable amount of cognitive load.
I think implicit conversions would avoid that cognitive load. Implicit conversions would probably be less confusing if the different representations have a consistent interface. In other words, it'd be less confusing to have an implicit conversion from borrowing Optional<T> to OptionalBorrow<T>, than to have an implicit conversion from borrowing Optional<T> to Optional<Borrow<T>>.
(Rust also has implicit conversions for different representations of the same high-level value, such as mutable-to-immutable reference coercion, Deref coercion, and unsized coercion. In both Rust and Swift, implicit conversions are reserved for special types and concepts; but unlike Rust, optionals are special in Swift.)
If Swift ever gets "ownership generics" in the future, maybe there could be a viable path to retrofit OptionalBorrow and OptionalInout (and MutableSpan), and APIs using these types. Retrofitting could be more difficult with Optional<Borrow<T>>, because it's already an Optional, but with a different interface than we might want.
typealias OptionalBorrow<T> = Optional<borrowing, T>
typealias OptionalInout<T> = Optional<inout, T>
typealias MutableSpan<T> = Span<inout, T>
Maybe it's worth explaining why I think "ownership generics" are a compelling enough future direction to warrant consideration. Basically, ownership generics would decouple being generic over ownership from being generic over types.
To make an analogy, Swift currently has two effects: throws and async. Technically, effects don't need to be built in to the language, because any effect could be expressed in the return type instead; for example, a throwing function could just be a function returning Result<T>.[1] A problem is that, by coupling effects to types, we lose the ability to be generic over an effect without also being generic over the return type. In contrast, the rethrows keyword (and a hypothetical reasync keyword) is a limited way for higher-order functions to be generic over effects. Rust doesn't have a similar feature, because it uses Result; a workaround is for higher-order functions (that don't just forward the return value) to have both throwing and non-throwing variants, such as reduce and try_reduce.[2]
Similarly, sometimes it's useful to be generic over ownership without also being generic over the return type. The most prominent example I can think of is properties and subscripts. Stored properties are automatically generic over ownership, and accessors are a limited way for computed properties and subscripts to be generic over ownership, where the ownership of the return value depends on the ownership of self. Rust doesn't have a similar feature; a workaround is for an "accessor function" to have a different variant for each kind of ownership, such as get_ref and get_mut.[3]
Generalized ownership generics could remove the need for collections to have both span and mutableSpan properties, remove the need for a noncopyable dictionary to have both "lookup and borrow" and "lookup and mutate" operations, and remove the need for optionals and collections to have both "borrowing map" and "consuming map" operations.
In functional programming jargon, an unbound generic type that represents an effect, such as
Result(or<T> => Result<T>), is called a "monad". ↩︎Another solution, used in functional programming languages like Haskell, is "higher-rank polymorphism", where code can be generic over unbound generic types, so code that would otherwise be generic over effects is instead generic over monads. ↩︎
Maybe it's also possible to represent an ownership kind as an unbound generic type, similar to how an effect can be represented as a monad:
borrowingis<T> => Borrow<T>,mutating/inoutis<T> => Inout<T>, andconsumingis<T> => T. ↩︎