Golang 型別轉換庫 cast

程式設計師小杜發表於2023-02-07

0 前言

你是否在使用 Go 的過程中因為型別轉換的繁瑣而苦惱過?

你是否覺得 Go 語言中的型別斷言可能會 panic 而對自己寫的程式碼有那麼一點點不放心?

如果你有過如上體驗,並且想要找到一個合適的解決方案的話,那麼本文推薦的一個用於型別轉換的第三方庫 cast 絕對是一個值得嘗試的選擇。

1 cast

cast 是一個極為簡潔的第三方庫,github 地址:https://github.com/spf13/cast

專案主頁裡的頭兩句介紹就是:

Easy and safe casting from one type to another in Go

Don’t Panic! ... Cast

可見,cast 的主要功能就是型別轉換,且沒有 panic

多說一句,Don't panic 在英語中本身就是一個常用語,表示不要慌張、不要害怕,所以,在這裡其實是一個有意思的雙關。

2 上手

2.1 安裝引入

這裡只講 go mod 的引入方式。

go.mod 檔案中 require github.com/spf13/cast v1.5.0(目前最新版為 1.5.0),接著用 mod 進行 downloadtidy 等操作,再在程式碼中 import "github.com/spf13/cast" 即可使用 cast 關鍵字使用 cast 的功能了。

2.2 使用

2.2.1 常規用法

我們直接透過幾個簡單的例子來體驗一下 cast:

var target interface{} = "123"
str := "hello, world!"

fmt.Println(cast.ToString(target))
fmt.Println(cast.ToInt(target))
fmt.Println(cast.ToInt(str))

// 輸出:
123
123
0

我們建立了一個 interface{} 型別的變數 target,傳統方式下如果要將一個 interface{} 轉化為 string,需要使用型別斷言:

var target interface{} = "123"

str := target.(string)
// or
str, ok := target.(string)

型別斷言的缺點很明顯,如果不接收第二個返回值,會有 panic 風險;如果接收第二個引數,則略顯繁瑣。

到了第二個 ToInt,cast 的優勢就更明顯了,傳統方式下,一個 interface{} 型別的 "123" 如果要轉換成 int,必須先型別斷言為 string,再使用 strconv 轉換成 int,程式碼就不寫了,想象一下就知道有多麻煩,而 cast 可以將這個過程一步到位。

接著是第三個輸出 cast.ToInt(str),這裡的 str 是一個 string 型別的 "hello, world!",它顯然不能被轉換成 int,於是 cast 將其設定為 int 的零值 0

其實 cast 的所有型別轉換都會將無法轉換的結果轉為零值,而不是 panic,這也就是 cast 官方承諾的 Don't panic

2.2.2 帶 error 的用法

看到這裡,有朋友可能要問了:如果我的邏輯必須判斷目標是否轉換成功了呢?如果我的轉換結果就有可能是 0 呢?我怎麼知道這個 0 是轉換失敗的零值,還是目標原始的真實值?

cast 的作者自然也想到了這一點,於是,cast 的所有型別轉換函式都有一個對應的 with error 版:

str := "hello"
strNum := "123"

num, e := cast.ToIntE(str)
fmt.Println(num)
fmt.Println(e)

num, e = cast.ToIntE(strNum )
fmt.Println(num)
fmt.Println(e)

// 輸出
0
unable to cast "hello" of type string to int64
123
nil

error 的版本其實就是在非 error 版的函式名結尾新增了一個 E,其結果也很好理解,這裡不再展開細講了。

2.2.3 很酷的東西

最後再來看一個我覺得很酷的東西:

var js interface{} = `{"name": "Jack", "gender": "male"}`

fmt.Println(cast.ToStringMap(js))

// 輸出
map[gender:male name:Jack]

cast 能直接將一個 JSON 字串轉換成 map!當然這一步其實用型別斷言也可以做到,但 cast 的方式會更加優雅。

3 效能及原理

如果你只是想使用 cast,那麼接下來的內容就可以忽略了;如果你還想深入瞭解一些 cast,可以看看這一節。

很多做後端開發的朋友會習慣性關心效能和原理,我也一樣,所以早在我第一次接觸使用 cast 時,我就去看了它的原始碼,然後……這樣,我直接把上面我們用過的 ToInt 的相關原始碼列出來,大家自己看看就明白了:

// cast.go

// ToInt casts an interface to an int type.
func ToInt(i interface{}) int {
    v, _ := ToIntE(i)
    return v
}

// caste.go

// ToIntE casts an interface to an int type.
func ToIntE(i interface{}) (int, error) {
    i = indirect(i)    // 這個 indirect 函式裡使用反射來獲取 i 的 interface{} 值,程式碼不列了
    // ...省略
    switch s := i.(type) {
    case int64:
        return int(s), nil
    case int32:
        return int(s), nil
    // ...省略
    case string:
        v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
        if err == nil {
            return int(v), nil
        }
        return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
    }
    // ...省略
}

明白了吧,沒有什麼奇技淫巧,依然是常規手段進行轉換,只是它把各種情況都囊括了進來,做到了足夠全面。

而且我們發現,帶 error 的函式才是原始函式,不帶 error 的只是一個封裝後的便捷方式。

cast 的原始碼很短,只有兩個檔案,加起來不到 2000 行。

所以看到這裡,cast 的效能問題就沒什麼值得討論的了,一定高不到哪兒去。尤其在泛型已經實裝了之後,泛型的效能要遠超型別斷言、反射之類的技術,因此大家在使用 cast 的時候也請視情況而定。

4 總結

cast 是我用了很多年的一個庫了,早在泛型還八字沒一撇的時候我就發現了這個庫,那時我們的專案程式碼裡充斥著許多 interface{} 和反射,cast 的確幫了我們很大的忙。儘管現在已經是泛型時代,go 語言可以用效能更佳的泛型替代許多以前只能用 interface{} 甚至反射實現的場景,但依然存在不少我們無法避免要用 interface{}型別轉換的地方,這種時候,尤其是這段程式對效能不敏感時,cast 依然是一把萬金油式的利器。

總結一下,cast 是一個用於型別轉換的 golang 第三方庫,它最大的特點是在型別轉換時可以不 panic,而是將出現問題的地方轉換成零值。當然,cast 也提供了帶 error 的函式,以供開發者在適當情況下使用。cast 的效能可能會是一個問題,因此我們在使用時一定要選擇合適的場景,避免由於濫用 cast 造成的效能瓶頸。

相關文章