Using discardableResult to allow closures to ignore the return part of a function

Hello,

This is my first topic and aimed to start a discussion, so any guidance to improve my activity is welcome :slight_smile:

I just encountered the following use case and I would like to know your thoughts about it.

Suppose I have the following simple function:

func validatePassword(_ password: String) -> Bool {
    password != "password"
}

and I wanted to use this function in two places. A place that cares about the return value and another place that doesn't.

// Cares about the returned Bool.
Button("Register", action: {})
    .disabled(!validatePassword(password))

// Doesn't care about the returned Bool.
SecureField("Password", text: $password)
    .onChange(of: password, perform: validatePassword)

Passing the function to perform: would not work and produce an error.

Error: Cannot convert value of type '(String) -> Bool' to expected argument type '(String) -> Void'

How would one go about reusing the same function logic in the two places?

I can think of one way to handle this specific case is adding a separate @State variable to disable the button.

I thought that Swift would be able to make this intuitive using @discardableResult

@discardableResult
func validatePassword(_ password: String) -> Bool {
    password != "password"
}

This also doesn't work, but I thought it would be useful if Swift can understand this as "If I pass this function to a function parameter that expects returning Void, please treat it like so because I used @discardableResult."

2 Likes

The only thing that @discardableResult does is suppress the compiler warning that occurs when you call the function and ignore the result. It does not change the return type of the function and it should not change the return type of the function. Such a language feature could easily lead to bugs.

That's incredibly easy: Just wrap the function in an anonymous closure:

SecureField("Password", text: $password)
    .onChange(of: password, perform: { validatePassword($0) })
2 Likes

I don’t understand the point of calling the validatePassword function and ignoring the result when it has no side effects. Is your example just simplified to the point of irrelevancy but your production code does have side effects?

Yes this is very simplified but you can imagine a similar function that does things like logging events, cashing, persisting some state, or storing to Keychain.

I agree that changing the return value is wrong, but it might be useful if there's some language feature that keeps the signature unchanged and instead allow it to be simply "ignored" or "discarded" to allow the function to be passed as an argument.

That's incredibly easy: Just wrap the function in an anonymous closure

your solution is great. I can imagine extending @discardableResult to mimic wrapping the same function in an anonymous closure like this.

If you're allowed to pass a function that returns a non-Void type to a parameter that expects a function that returns a Void type, then you're effectively changing the signature of the function.

Given how easy and concise it is to wrap a function in an anonymous closure, I just don't see this feature as necessary.