面試官提問三個 Go 接口的概念,10 年 gopher 竟無言以對

自 Go 1.18 後, Go 的 interface 的含義有所變化, 三個新的和 Go 接口有關的概念很多人還不知道: type set(類型集合)、specific type(特定類型) 和**structural type**(結構類型)。

1 type set (類型集合)

type set 稱之爲類型集合,一些關注 Go 泛型的朋友其實也對此有些瞭解,它是 Go 1.18 新增加的一個概念。

Go 1.18 之前,Go 的接口代表了一組方法的集合 (method set), 凡是實現了這些方法集合的類型,都被稱之爲實現了這個接口。Go 不像 Java 語言,需要顯示地定義某個類實現某個接口,Go 不需要這樣,在 Go 中,只要一個類型實現了某個接口定義的所有方法,它就實現了這個接口,可以賦值給這個接口類型的變量,或者作爲這個接口類型的方法的實參或者返回值,這種設計有時候也被叫做**鴨子類型**(duck typing)。只要它走起來像鴨子,叫起來像鴨子,那麼它就是鴨子,這是一個很經典的對鴨子類型的描述。

在 Go 1.18 中,接口不再代表方法的集合了,而是代表類型的集合 (type set)。只要某種類型在這個接口的類型集合中,那麼我們就說這種類型實現了這個接口。如果這個接口被用作類型約束,那麼在這個接口定義的類型集合中的任意一個元素,都可以實例化這個類型參數。

所以,實際上,爲了支持接口作爲類型約束的擴展,Go 語言規範不得不重新定義接口的含義了,這也是類型集合出現的原因。

其實,接口的方法集的概念還在 一個接口的方法集是這個接口的類型集合中所有元素的方法集的交集

本文假定你對 Go 泛型有了一定的瞭解。假定你還不瞭解,那麼你必須知道,Go 1.18 中接口除了原先的方法元素還,還支持包含類型元素,類型元素可以是某個類型T、或者是近似類型~T, 或者是它們的聯合int|int8|int16|int32|int64|~string

如果一個接口I嵌入了另外一個接口E, 那麼I的類型集是它顯示定義的類型集合和嵌入的接口E的類型集合的交集。相當於E把接口I的類型集收窄了。

如何判斷一個接口的類型集呢?請遵循下面的原則:

  1. 一個方法的類型集合是定義這個方法的所有類型,也就是隻要某個類型的方法集包含這個方法,那麼它就屬於這個方法的類型集合

    比如接口中有String() string這樣一個方法,那麼所有實現這個方法的類型都屬於String() string定義的類型集合,比如net.IP

  2. 一個非接口類型的類型集合就是隻包含這個類型的類型集合

    比如int的類型集合只包含int這樣一個元素

  3. 近似元素~T的類型集合是所有底層類型爲T的所有類型的集合

    比如type MyInt int中的MyInt就屬於~int的類型集合

  4. 聯合元素t1|t2|…|tn的類型集合是這些聯合元素類型集合的並集

下面的例子列舉了一些類型的集合:

2 specific type (特定類型) 和 specific type set

接口另外一個很重要的概念就是specific type (特定類型)。

只有包含類型元素的接口才定義了特定類型 (可能是空的類型)。

如果不嚴格的講,特定類型是出現在類型元素中定義的那些類型T~Tt1|t2|...|tn中的t1t2...tn

更準確地說,對於給定的接口I,特定類型的集合對應於該接口代表的類型集合𝑅,這裏要求𝑅是非空且有限的。否則,如果𝑅爲空或無限,則接口沒有特定類型。

對於一個給定的接口、類型元素或者類型,它代表的類型集合𝑅定義如下:

下面是特定類型的例子:

type set vs specific type set

類型集合和特定類型集合還是有區別的,從上面它們的定義可以看出來。

一個接口即使類型爲空,它的特定類型集合可能不爲空。
比如interface{ int; m() }, 它的類型集合是空的 (int 沒有實現 m 方法),但是它的特定類型是int

一個接口即使有有限的特定類型,它的類型集合也可能是無限的
比如interface{ ~int; m() }, 它的特定類型是 int,但是它的類型集合確是無限的 (任何底層爲 int 並且實現了方法 m 的類型都屬於它的類型集合)

那麼定義特定類型有什麼用呢?

特定類型的應用

特定類型主要用於判斷類型參數是否支持索引, 像a[x]這樣的類型。

比如一個表達式a[x]a這個實例的類型可能是數組、指向數組的指針、slice、字符串、map。

如果a的類型是類型參數P的話,那麼我們的代碼a[x]在什麼條件下才不會編譯出錯?

要求的條件就和特定類型有關了:

特定類型還用作類型轉化定義上。

對於一個變量x, 如果它的類型是V, 要轉換成的類型是T, 只要滿足下面一條,x 就可以轉換成T類型:

一句話,是類型參數就滿足每一個特定類型,不是類型參數就滿足這個類型。

另外,對於類型參數,要調用內建的函數lencap,必須要求它們的特定類型允許使用這些內建函數。

3 structural type (結構類型)

一個接口T要被成爲結構化的 (structural), 需要滿足下面的條件之一:

  1. 存在一個單一的類型U, 它是T的類型集合中的每一個元素相同的底層類型

  2. T的類型集合只包含 chan 類型,並且它們的元素類型都是E, 所有的 chan 的方向包含相同的方向 (不一定要求完全相同)

結構化類型包含一個結構類型,根據上面的條件不同,結構類型可能是:

  1. 類型U, 或者

  2. 如果T只包含雙向 chan 的話,結構類型爲chan E, 否則可能是chan<- E或者<-chan E

下面是包含結構類型的結構化接口:

在 Go 語言規範中,並沒有對結構化接口有更多的介紹,如何使用,更多是是它內部獲取底層的結構類型,以及做類型檢查,比如下面的例子就會報no structural type編譯錯誤:

下面的代碼也會報M has no structural type編譯錯誤:

比如下面大家使用 Go 泛型的時候常會犯的錯誤, 雖然[]bytemap[int]bytestring都能 range, 而且 key(index)、value 類型都一樣,但是也會報R has no structural type錯誤:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zKnh_iPm8skxWv3rxaOscw