Any reason why `@discardableResult` can't be applied to closures?

Consider this code:

let f: () -> String = { "discardable" }
f()

Compiling it produces result of call to function returning 'String' is unused warning.

Rewriting it as

let f: @discardableResult () -> String = { "discardable" }
f()

is not allowed.

The use case is the dynamicMember API for bridging to a language where return results are absent most of the time, but are still expressed in Swift with .undefined enum case. This API returns a closure, which it would be great to mark as @discardableResult if it were possible.

Has this been considered before? If so, what are the downsides?

11 Likes

@Max_Desiatov interesting question. I've used callAsFunction to achieve this before but didn't really love it.

I think the biggest additional complexity would come from the additional conversions needed to make this a type attribute. Is there an implicit conversion from () -> Int to @discardableResult () -> Int? How about the other way around? Should the compiler warn on one or the other? How do you silence such a warning?

This can all be sidestepped by putting the @discardableResult on the let (or on a parameter) rather than on the type. That would be an almost trivial addition, but wouldn't handle your original motivating use case where you want it for a return value.

3 Likes

Yes, sure.

Makes sense to me to allow both implicitly.

I don't think there's a need to warn about the implicit conversion. @discardableResult by itself is explicit enough to signal user's intent. Additional warnings about conversions with this attribute would only make things more confusing, in my opinion.

1 Like

If there is another overload of the subscript that returns a (ConvertibleToJSValue...) -> Void, does Swift pick up the right one when the return value is ignored?

IMO it's not so much whether Jordan's questions are answerable for a specific use case as much as whether these answers make sense for everyone (or at least a large majority) of people who could want to add @discardableResult to a function type. I personally wouldn't know how to evaluate whether these are the right decisions without more projects stepping forward with related use cases.

In what sense, then, is @discardableResult a type-level attribute under this scheme? Isn’t this indistinguishable in behavior from @discardableResult being an attribute of the binding?

An attribute on the binding is what makes sense with the limited example I gave in the original post. I'm looking for a broader behavior, where we have a function returning closures. I'd like results returned by such closures to be discardable:

enum JSValue {
  case string(String)
  // ...
  case undefined
}

// body of `closureFactory` made trivial for demonstration purposes
func closureFactory() -> (() -> JSValue) {
  { .undefined }
}

// next line triggers a warning about discarded result
closureFactory()() 

// have to write it this way to avoid the warning
_ = closureFactory()()

With this example it is clear that what you want is a new type attribute as Jordan said. A declaration attribute won't help here, because it won't be seen outside of the function:

func closureFactory() -> (() -> JSValue) {
    
    @discardableResult func closure() -> JSValue { .undefined }
    
    return closure
}

// the next line still triggers a warning about discarded result
closureFactory()()
1 Like

Perhaps Max means that in the following simplified example:

func foo() -> Int { 0 }
@discardableResult func bar() -> Int { 0 }

let x = foo
x() // Warning: Result of call to function returning 'Int' is unused

let y = bar
y() // Warning: Result of call to function returning 'Int' is unused

if discardableResult is moved to the type attribute the warning for y() would disappear (which makes total sense to me). @Max_Desiatov, correct me if I got you wrong.