驚了!原來Go語言也有隱式轉型

語言: CN / TW / HK

本文永久鏈接 – https://tonybai.com/2021/12/02/go-has-implicit-type-convertion

我的極客時間專欄 《Go語言第一課》上線 後收到了很多學員的反饋,大家提出了很多顯然是經過認真思考的高水平問題。有些時候我也會被這些問題所“難倒”,比如昨天我在後台看到的這個問題。

我把這個問題整理為下面代碼文本,方便大家copy和重現問題:

package main

type MyInt int
type MyMap map[string]int

func main() {
    var x MyInt
    var y int
    x = y     // 會報錯: cannot use y (type int) as type MyInt in assignment
    _ = x 

    var m1 MyMap
    var m2 map[string]int
    m1 = m2 // 不會報錯
    m2 = m1 // 不會報錯
}

結合上面代碼,我將這位學員的問題重新描述一下: MyInt與int是不同的兩個類型,MyMap與map[string]int也是不同的兩個類型,為何將int型變量賦值給MyInt型變量時需要做顯式轉型,而將map[string]int變量賦值給MyMap型變量就不需要顯式轉型呢

我們知道:Go是強調類型安全的靜態編譯型語言,在Go語言中,不同類型變量是不能在一起進行混合計算的,這是因為Go希望開發人員明確知道自己在做什麼,這與C語言的“信任程序員”原則完全不同,因此你需要 以顯式的方式通過轉型統一參與計算各個變量的類型 。 比如:上面問題中MyInt雖然底層類型(underlying type)是int,但MyInt與int是兩個不同的類型,因此它們之間的相互賦值需要通過顯式轉型來進行,否則Go編譯器將報錯,這個沒有任何疑問。

估計此時大家也都會異口同聲的問:那“m1 = m2”呢?為何這一句不需要顯式轉型呢?MyMap的底層類型是map[string]int,但MyMap與map[string]int也是兩個不同的類型啊!千萬不要吿訴我:int與map[string]int這兩個原生類型的待遇有不同!

事實上這個問題的關鍵就在於 int與map[string]int的確有不同

在Go中,我們定義一個類型一般通過type關鍵字進行,比如:

type T1 int
type T2 T1

在Go中,使用上述類型聲明語句定義的類型T1、T2被稱為 defined type ,中文稱為“具定義類型”。在type alias加入Go之前,這種類型還被稱為named type(具名類型),顧名思義, 這個類型是有名字的 。這個其實也很好理解。但 問題的關鍵是Go語言的原生類型是否都是defined type

好在 Go語言規範 中對各個內置的原生類型做了明確規定:

  • 所有數值類型都是defined type;(這裏面就包含int)
  • 字符串類型string是defined type;
  • 布爾類型bool是defined type。

就這些,沒了?沒了!這就 意味着map、數組、切片、結構體、channel等原生複合類型(composite type)都不是defined type

我們離真相越來越近了!我們再回到最初的問題中。int與MyInt都是defined type,因此它們兩者之間相互賦值是需要顯式轉型的。map[string]int不是defined type,MyMap是defined type,那麼它們直接的賦值是怎麼規定的呢?

Go語言規範中 關於Assignability的規則 中有下面這一條規定:

x's type V and T have identical underlying types and at least one of V or T is not a defined type.
如果x的類型V與類型T具有相同的底層類型,並且V和T至少有一個不是defined type,那麼x可以賦值給類型T的變量。

我們用問題中的代碼來套一下這個規則。我們有一個MyMap類型的變量m1,MyMap類型與map[string]int類型具有相同的底層類型map[string]int,並且map[string]int類型不是一個defined type,那麼我們可以將m1直接賦值給map[string]int類型的變量m2,反之亦可。

到這裏,上面的問題算是解答完畢了。我們再來擴展一下,看一些Go其他原生但非defined type的類型賦值的例子,例子中這些賦值都不會報編譯錯誤:

package main

type MyMap map[string]int
type MySlice []byte
type MyArray [10]int
type MyStruct struct {
    a int
    b string
}
type MyChannel chan int

func main() {
    var m1 MyMap
    var m2 map[string]int
    m1 = m2 // 不會報錯
    m2 = m1 // 不會報錯

    var sl1 MySlice
    var sl2 []byte
    sl1 = sl2 // 不會報錯
    sl2 = sl1 // 不會報錯

    var arr1 MyArray
    var arr2 [10]int
    arr1 = arr2 // 不會報錯
    arr2 = arr1 // 不會報錯

    var s1 MyStruct
    var s2 struct {
        a int
        b string
    }
    s1 = s2 // 不會報錯
    s2 = s1 // 不會報錯

    var c1 MyChannel
    var c2 chan int
    c1 = c2 // 不會報錯
    c2 = c1 // 不會報錯
}

對於上面這種在底層類型相同且至少有一個類型不是defined type的兩個類型變量間賦值的情況,是不是很眼熟。沒錯,它和 Go的無類型常量隱式轉型 十分相似,雖然背後的原理是不同的:

type MyInt int
const a = 1234
var n MyInt = a

Go總體來説是推崇顯式哲學的,那怎麼來理解這種隱式轉型呢?我覺得至少有兩點:

首先這種轉型更多是在編譯器保證類型安全性的前提下進行的,不會出現溢出或未定義行為。

其次,這種隱式轉型一定程度減少了代碼輸入,對開發體驗的提升有幫助。

最後,感謝 《Go語言101》 作者老貘兄在這個問題上給予我的點撥,國內在Go語言語法細節上理解最到位最深入的人非老貘兄莫屬^_^。

“Gopher部落”知識星球 正式轉正(從試運營星球變成了正式星球)!“gopher部落”旨在打造一個精品Go學習和進階社羣!高品質首發Go技術文章,“三天”首發閲讀權,每年兩期Go語言發展現狀分析,每天提前1小時閲讀到新鮮的Gopher日報,網課、技術專欄、圖書內容前瞻,六小時內必答保證等滿足你關於Go語言生態的所有需求!部落目前雖小,但持續力很強,歡迎大家加入!

我愛發短信 :企業級短信平台定製開發專家 https://51smspush.com/。smspush : 可部署在企業內部的定製化短信平台,三網覆蓋,不懼大併發接入,可定製擴展; 短信內容你來定,不再受約束, 接口豐富,支持長短信,簽名可選。2020年4月8日,中國三大電信運營商聯合發佈《5G消息白皮書》,51短信平台也會全新升級到“51商用消息平台”,全面支持5G RCS消息。

著名雲主機服務廠商DigitalOcean發佈最新的主機計劃,入門級Droplet配置升級為:1 core CPU、1G內存、25G高速SSD,價格5$/月。有使用DigitalOcean需求的朋友,可以打開這個 鏈接地址 :https://m.do.co/c/bff6eed92687 開啟你的DO主機之路。

Gopher Daily(Gopher每日新聞)歸檔倉庫 – https://github.com/bigwhite/gopherdaily

我的聯繫方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公眾號:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知識星球:https://public.zsxq.com/groups/51284458844544

微信讚賞:

商務合作方式:撰稿、出書、培訓、在線課程、合夥創業、諮詢、廣吿合作。

© 2021,bigwhite. 版權所有.