【问题标题】:How can I restore type generic type inference in Swift result builders?如何在 Swift 结果构建器中恢复类型泛型类型推断?
【发布时间】:2026-02-09 20:35:01
【问题描述】:

在设计使用和生成泛型类型的结果构建器时,我经常发现自己需要以一种令人讨厌的显式方式拼写类型。考虑这个玩具示例:

@resultBuilder enum SomeBuilder {
    static func buildBlock(_ v: Result<Int,Error>) -> Result<Int,Error> { v }
}
struct SomeStruct {
    init(@SomeBuilder _ v: () -> Result<Int,Error>) {}
}

let x = SomeStruct {
    Result.success(0)
}

SomeBuilder 几乎是最简单的@resultBuilder:它接受Result&lt;Int,Error&gt; 类型的单个值并按原样返回它们。 SomeStruct 是一个简单的结构体,它带有一个初始化器,它接受一个产生Result&lt;Int,Error&gt;@SomeBuilder 闭包。因此,对我来说,应该编译这段代码是有意义的。 Swift 应该能够找出 Result.success(0) 指的是 Result&lt;Int,Error&gt;.success(0) 因为这是唯一在这种情况下有意义的类型。但是,此示例无法编译

_:9:5: error: cannot convert value of type 'Result<Int, _>' to expected argument type 'Result<Int, Error>'
    Result.success(0)
    ^
_:9:5: note: arguments to generic parameter 'Failure' ('_' and 'Error') are expected to be equal
    Result.success(0)
    ^
_:9:5: error: generic parameter 'Failure' could not be inferred
    Result.success(0)
    ^
_:9:5: note: explicitly specify the generic arguments to fix this issue
    Result.success(0)
    ^
          <Int, <#Failure: Error#>>

在同一行中,Swift 声称它无法推断出Result.success(0)Failure 类型,然后声称它知道失败一定是Error。我可以通过使用来编译这段代码

let x = SomeStruct {
    Result<Int,Error>.success(0)
}

但在实际示例中,对类型如此冗长对我要设计的 DSL 极为不利。

这是编译器中的错误吗?如何让 Swift 推断出结果构建器(例如这个)中的泛型类型?

【问题讨论】:

    标签: swift generics


    【解决方案1】:

    我相当有信心这应该被归类为错误,但我确实有解释和解决方案。考虑一下 Swift 如何转换这个结果生成器。

    let x = SomeStruct {
        Result.success(0)
    }
    

    变成(或多或少)

    let x = SomeStruct {
        let v0 = Result.success(0)
        return SomeBuilder.buildBlock(v0)
    }
    

    单独来看,编译器无法推断v0 的类型。这只有在用作 buildBlock 的参数时才知道,但 Swift 不会从以后的使用中推断声明的类型。

    但是,如果我们在SomeBuilder 中添加一个微不足道的buildExpression,这种转换就不同了。

    @resultBuilder enum SomeBuilder {
        static func buildExpression(_ v: Result<Int,Error>) -> Result<Int,Error> { v }
        static func buildBlock(_ v: Result<Int,Error>) -> Result<Int,Error> { v }
    }
    

    现在,Swift 转换

    let x = SomeStruct {
        Result.success(0)
    }
    

    进入(或多或少)

    let x = SomeStruct {
        let v0 = SomeBuilder.buildExpression(Result.success(0))
        return SomeBuilder.buildBlock(v0)
    }
    

    在这段代码中,Swift 能够推断出v0 的类型是Result&lt;Int,Error&gt;,因为这是buildExpression 返回的类型,Swift 能够推断出Result.success(0) 的类型是@987654335 @ 因为这是 buildExpression 作为参数的类型。

    有关这些@resultBuilder 转换的详细信息,请参阅令人难以置信的WWDC session 关于结果生成器。

    结论

    @resultBuilder enum SomeBuilder {
        static func buildExpression(_ v: Result<Int,Error>) -> Result<Int,Error> { v }
        static func buildBlock(_ v: Result<Int,Error>) -> Result<Int,Error> { v }
    }
    struct SomeStruct {
        init(@SomeBuilder _ v: () -> Result<Int,Error>) {}
    }
    
    let x = SomeStruct {
        Result.success(0)
    }
    

    编译成功。如果您希望泛型类型推断在结果构建器中工作,就像您在语言的其他部分中所习惯的那样,您需要有一个 buildExpression 采用相关类型,即使它只是返回其输入,即使您否则根本不需要任何buildExpressions!完成此操作后,您的结果构建器将更具可读性,因为当您从上下文中清除类型时,您将无需不必要地拼写类型。这适用于复杂的构建器,而不仅仅是像这样的玩具示例。

    【讨论】: