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.
jrose
(Jordan Rose)
3
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.
xwu
(Xiaodi Wu)
6
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()()
hooman
(Hooman Mehr)
8
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
tera
9
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.