Help me with writing generic func on extension SwiftUI.Binding:

import SwiftUI

// I want to convert this to a func in extension Binding. See below
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = $0 }
    )
}

// but this doesn't compile
extension Binding {
    public func ifNilDefault<T>(to: T) -> Binding<T> where Value == Optional<T> {
        Binding { wrappedValue ?? to } set: { wrappedValue = $0 }   // error: Cannot convert return expression of type 'Binding<Optional<T>>' to return type 'Binding<T>'
    }
}
1 Like

Not sure, but it seems that when you write a generic type name without specifying type parameters, the type inference prefers:

where Value == Optional<T>
               👆

over:

-> Binding<T>
           👆

In other words, the type inference is doing the same thing as when you write something like this:

extension Array where Element == Int {
    func f() -> some Collection { Array() }
}

but in your case it's overruling the explicit return type, which is counterintuitive. If you add <T> to the return expression then it does what you meant.

1 Like

I think the error is because of implicit promotion to optional. I wonder why the type inference doesn't just prioritize with the return type? There is really no need to infer. The return type is clearly there.

1 Like

It's not due to optional promotion, but rather that the mention of a bare Binding inside the extension really means Binding<Value>, which due to your constraint is Binding<T?>.

The following extension on array shows the problem more simply:

extension Array {
  func doSomething<NewElement>() -> Array<NewElement> {
    return Array() // 🛑 Cannot convert return expression of type 'Array<Element>' to return type 'Array<NewElement>'
  }
}

Using Array in the body of doSomething does not get its inference from the return type, but rather from the extension type.

When you want the type to be inferred from the return type you can use .init:

extension Binding {
  public func ifNilDefault<T>(to: T) -> Binding<T> where Value == Optional<T> {
    .init { wrappedValue ?? to } set: { wrappedValue = $0 }
  }
}
4 Likes

Thanks for explaining! I understand now.

1 Like

Although I like and use the .init style, there is one more alternative: a type placeholder. These are all equivalent:

.init
Binding<_>
Binding<T?>
2 Likes