I have a C library with a uniform API. Using it in swift, every function call would roughly look like this:
let error = FooError()
let res = foo_func(arg1, arg2, &error)
if (foo_is_error(&error)) {
return .failure(SwiftFooError(&error))
}
return .success(res)
This becomes quite verbose. Coming from C++ I thought I could write a function akin to:
template<typename F, typename... Args>
auto func_to_result(F&& func, Args&&... args) -> Result<std::invoke_result_t<F, Args...>, FooError> {
FooError err;
auto res = std::invoke(std::forward<F>(func), std::forward<Args>(args)..., &err);
if (foo_is_error(&err)) {
return Result::error(err);
}
return Result::success(res);
}
My latest attempt looks as follows:
func funcToResult<T, each Args>(
_ function: (repeat each Args, UnsafeMutablePointer<FooError>) -> T,
args: repeat each Args
) -> Result<T, SwiftFooError> {
var error = FooError()
let result = function(repeat each args, &error)
if foo_is_error(&error) {
return .failure(SwiftFooError.init(error))
}
return .success(result)
}
I also tried standard parameter packs (Any...) and taking in an array ([Any]) for the parameters. None of the approaches work.
From my beginner's perspective, the errors appear to be as follows:
with repeat each args: Swift cannot call a non-repeat-each function with repeat-each arguments
with Any...: Swift doesn't manage to differentiate between the Error parameter and the Any... parameters
with [Any]: The function signature at the callsite doesn't match anymore
How would I go about writing this helper? Perhaps a macro is in order?
Functions will generate relevant errors. Closures will not (yet).
// Error: A parameter following a variadic parameter requires a label
func f<each T>(t: repeat each T, _: Void) { }
I don't understand why this is required, when only one pack is used. Perhaps someone can explain it, as I don't think the proposal does.
Two choices:
Tupleize the arguments.
func funcToResult<T, each Arg>(
_ function: ((repeat each Arg), UnsafeMutablePointer<FooError>) -> T,
_ arg: repeat each Arg
) throws(SwiftFooError) -> T {
var error = FooError()
let result = function((repeat each arg), &error)
guard !foo_is_error(&error) else { throw .init(error) }
return result
}
Reverse the closure's parameters.
func funcToResult<T, each Arg>(
_ function: (UnsafeMutablePointer<FooError>, repeat each Arg) -> T,
_ arg: repeat each Arg
) throws(SwiftFooError) -> T {
var error = FooError()
let result = function(&error, repeat each arg)
guard !foo_is_error(&error) else { throw .init(error) }
return result
}
Except for trailing closures (and this is fixed in Swift 6), Swift processes arguments left to right. If Swift allowed a function to be declared like so:
Oh, I didn't mean ambiguity to the compiler. I meant actual ambiguity.
The situation we have now, where the first of these compiles, and the second does not, will lead to more threads like this one. I don't think it's right for either to be allowed if the other is not.
func f<each T>(_: Void, _: repeat each T) { }
func f<each T>(_: repeat each T, _: Void) { }
This is a problem with variadic parameters, too. I don't think they've proven themselves to be nearly as important, but also should be fixed.
I don't see this as a problem, and there's definitely cases where a non-optional first parameter followed by a optional list of other parameters is useful. And once same type requirements on parameter packs are implemented variadic parameters can be replaced by parameter packs, and then we don't have to deal with the splatting problem anymore.
It would be nice if we could allow that second kind of function to compile, but removing the first kind because we can't is not a useful consistency.
(repeat each Args, UnsafeMutablePointer<FooError>) -> T
above, all of the relevant possibilities are potentially useful. Forcing argument position because of this left-to-right business is programmer-unfriendly at best. We don't even need default arguments for the problems to arise anymore, as with the pre-parameter pack days…
f("")
works fine with either of these:
func f<T>(_: T, _: Int = 0) { }
func f<each T>(_: repeat each T, labeled: Int = 0) { }
…but with either of these…
func f<T>(_: Int = 0, _: T) { }
func f<each T>(_: Int = 0, _: repeat each T) { }
…Cannot convert value of type 'String' to expected argument type 'Int'