According to the swift language guide, I need to implement buildBlock method when I define a result builder. And the guide also says:
" static func buildBlock(_ components: Component...) -> Component
Combines an array of partial results into a single partial result. A result builder must implement this method. "
Obviously, the parameter type and the return type should be the same. However, if I use different types, it also seems to be no problem. I write some simple code below:
@resultBuilder
struct DoubleBuilder {
static func buildBlock(_ components: Int) -> Double {
3
}
}
It compiles successfully in Xcode. Can the types be different?It seems like to be inconsistent with the official guide.
EDIT:
The parameter type of buildBlock method in ViewBuilder in SwiftUI is also different from the return type. For example:
static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>
CodePudding user response:
When they say
static func buildBlock(_ components: Component...) -> Component
They don't strictly mean that there exists one type, Component
, that buildBlock
must take in and return. They just mean that buildBlock
should take in a number of parameters, whose types are the types of "partial results", and return a type that is a type of a "partial result". Calling them both Component
might be a bit confusing.
I think the Swift evolution proposal explains a little better (emphasis mine):
The typing here is subtle, as it often is in macro-like features. In the following descriptions,
Expression
stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result),Component
stands for any type that is acceptable for a partial or combined result to have, andFinalResult
stands for any type that is acceptable to be ultimately returned by the transformed function.
So Component
, Expression
and FinalResult
etc don't have to be fixed to a single type. You can even overload the buildXXX
methods, as demonstrated later in the evolution proposal.
Ultimately, result builders undergo a transformation to calls to buildXXX
, when you actually use them. That is when type errors, if you have them, will be reported.
For example,
@resultBuilder
struct MyBuilder {
static func buildBlock(_ p1: String, _ p2: String) -> Int {
1
}
}
func foo(@MyBuilder _ block: () -> Double) -> Double {
block()
}
foo { // Cannot convert value of type 'Int' to closure result type 'Double'
10 // Cannot convert value of type 'Int' to expected argument type 'String'
20 // Cannot convert value of type 'Int' to expected argument type 'String'
}
The first error is because the result builder's result type is Int
, but foo
's closure parameter expects a return value of type Double
. The last two is because the transformation of the result builder attempts to call MyBuilder.buildBlock(10, 20)
.
It all makes sense if you look at the transformed closure:
foo {
let v1 = 10
let v2 = 20
return MyBuilder.buildBlock(v1, v2)
}
Essentially, as long as the transformed code compiles, there is no problem.