Go 1.17 泛型尝鲜
<img src="https://www.4async.com/2021/08/golang-117-generics/go.png" alt="Featured image of post Go 1.17 泛型尝鲜" /><p>今天,Go的1.17版本终于正式发布,除了带来各种优化和新功能外,1.17正式在程序中提供了尝鲜的泛型支持,这一功能也是为1.18版本泛型正式实装做铺垫。意味着在6个月后,我们就可以正式使用泛型开发了。那在Go 1.18正式实装之前,我们在1.17版本中先尝鲜一下泛型的支持吧。</p>
<h2 id="泛型有什么作用">泛型有什么作用?
</h2><p>在使用Go没有泛型之前我们怎么实现针对多类型的逻辑实现的呢?有很多方法,比如说使用<code>interface{}</code>作为变量类型参数,在内部通过类型判断进入对应的处理逻辑;将类型转化为特定表现的鸭子类型,通过接口定义的方法实现逻辑整合;还有人专门编写了Go的<a class="link" href="https://twitter.com/yogthos/status/883058510275149826" target="_blank" rel="noopener"
>函数代码生成工具</a>,通过批量生成不同类型的相同实现函数代替手工实现等等。这些方法多多少少存在一些问题:使用了<code>interface{}</code>作为参数意味着放弃了编译时检查,作为强类型语言的一个优势就被抹掉了。同样,无论使用代码生成还是手工书写,一旦出现问题,意味着这些方法都需要重复生成或者进行批量修改,工作量反而变得更多了。</p>
<p>在Go中引入泛型会给程序开发带来很多好处:通过泛型,可以针对多种类型编写一次代码,大大节省了编码时间。你可以充分应用编译器的编译检查,保证程序变量类型的可靠性。借助泛型,你可以减少代码的重复度,也不会出现一处出现问题需要修改多处地方的尴尬问题。这也让很多测试工作变得更简单,借助类型安全,你甚至可以少考虑很多的边缘情况。</p>
<p>Go语言官方有详细的泛型提案文档可以在<a class="link" href="https://go.googlesource.com/proposal/+/refs/heads/master/design/15292-generics.md" target="_blank" rel="noopener"
>这里</a>和<a class="link" href="https://go.googlesource.com/proposal/+/refs/heads/master/design/15292/2013-12-type-params.md" target="_blank" rel="noopener"
>这里</a>查看详情。</p>
<h2 id="如何使用泛型">如何使用泛型
</h2><p>前面理论我们仅仅只做介绍,这次尝鲜还是以实践为主。让我们先从一个小例子开始。</p>
<h3 id="从简单的例子开始">从简单的例子开始
</h3><p>让我们先从一个最简单的例子开始:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"fmt"</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Addable</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">type</span> <span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">int8</span>, <span style="color:#66d9ef">int16</span>, <span style="color:#66d9ef">int32</span>, <span style="color:#66d9ef">int64</span>,
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">uint</span>, <span style="color:#66d9ef">uint8</span>, <span style="color:#66d9ef">uint16</span>, <span style="color:#66d9ef">uint32</span>, <span style="color:#66d9ef">uint64</span>, <span style="color:#66d9ef">uintptr</span>,
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">float32</span>, <span style="color:#66d9ef">float64</span>, <span style="color:#66d9ef">complex64</span>, <span style="color:#66d9ef">complex128</span>,
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</span>[<span style="color:#a6e22e">T</span> <span style="color:#a6e22e">Addable</span>](<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#a6e22e">T</span>) <span style="color:#a6e22e">T</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">b</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">add</span>(<span style="color:#ae81ff">1</span>,<span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">add</span>(<span style="color:#e6db74">"1"</span>, <span style="color:#e6db74">"2"</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>这个函数可以实现任何需要使用<code>+</code>符号进行运算的类型,我们通过定义<code>Addable</code>类型,枚举了所有可能可以使用<code>add</code>方法的所有的类型。比如我们在<code>main</code>函数中就使用了<code>int</code>和<code>string</code>两种不同类型。</p>
<p>但是如果这时我们使用简单的<code>go run</code>命令运行,会发现提示语法错误:</p>
<pre tabindex="0"><code>$ go version
go version go1.17 darwin/arm64
$ go run ~/main.go
# command-line-arguments
../main.go:8:2: syntax error: unexpected type, expecting method or interface name
../main.go:15:6: missing function body
../main.go:15:9: syntax error: unexpected [, expecting (
</code></pre><p>因为在Go 1.17中,泛型并未默认开启,你需要定义<code>gcflags</code>方式启用泛型:</p>