搜尋
首頁後端開發GolangGo 泛型:深入探討

Go Generics: A Deep Dive

1. 不使用泛型

在引入泛型之前,有幾種方法來實現支援不同資料類型的泛型函數:

方法 1:為每個資料型別實作一個函數
這種方式會導致程式碼極度冗餘和維護成本高昂。任何修改都需要對所有函數執行相同的操作。而且,由於Go語言不支援同名函數重載,因此暴露這些函數供外部模組呼叫也不方便。

方法二:使用範圍最大的資料型別
為了避免程式碼冗餘,另一種方法是使用範圍最大的資料類型,即方法2。典型的例子是math.Max,它會傳回兩個數字中較大的一個。為了能夠比較各種數據類型的數據,math.Max使用了Go中數值類型中範圍最大的float64數據類型作為輸入和輸出參數,從而避免了精度損失。雖然這在一定程度上解決了程式碼冗餘問題,但任何類型的資料都需要先轉換為float64類型。例如,當比較 int 和 int 時,仍然需要進行類型轉換,這不僅會降低效能,而且看起來不自然。

方法 3:使用介面{}型別
使用interface{}類型有效解決了上述問題。然而,interface{}類型引入了一定的運行時開銷,因為它需要在運行時進行類型斷言或類型判斷,這可能會導致一些效能下降。另外,當使用interface{}類型時,編譯器無法進行靜態類型檢查,因此某些類型錯誤可能只能在執行時發現。

2. 泛型的優點

Go 1.18 引入了對泛型的支持,這是 Go 語言開源以來的重大變化。
泛型是程式語言的一個特性。它允許程式設計師在程式設計中使用泛型類型而不是實際類型。然後在實際呼叫時透過明確傳遞或自動推導,取代泛型類型,達到程式碼重複使用的目的。在使用泛型的過程中,將要操作的資料類型指定為參數。這樣的參數類型在類別、介面和方法中分別稱為泛型類別、泛型介面和泛型方法。
泛型的主要優點是提高程式碼的可重複使用性和類型安全性。與傳統的形式參數相比,泛型使得編寫通用程式碼更加簡潔靈活,提供了處理不同類型資料的能力,進一步增強了Go語言的表達能力和復用性。同時,由於泛型的具體類型是在編譯時確定的,因此可以提供類型檢查,避免類型轉換錯誤。

3. 泛型和介面的差別{}

在Go語言中,interface{}和泛型都是處理多種資料類型的工具。為了討論它們的差別,我們先來看看interface{}和泛型的實作原理。

3.1 interface{}實作原理

interface{} 是一個空接口,介面類型中沒有方法。由於所有類型都實作了interface{},因此它可用於建立可接受任何類型的函數、方法或資料結構。 interface{}在執行時的底層結構表示為eface,其結構如下所示,主要包含_type和data兩個欄位。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type type struct {
    Size uintptr
    PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
    Hash uint32 // hash of type; avoids computation in hash tables
    TFlag TFlag // extra type information flags
    Align_ uint8 // alignment of variable with this type
    FieldAlign_ uint8 // alignment of struct field with this type
    Kind_ uint8 // enumeration for C
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    Equal func(unsafe.Pointer, unsafe.Pointer) bool
    // GCData stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, GCData is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    GCData *byte
    Str NameOff // string form
    PtrToThis TypeOff // type for pointer to this type, may be zero
}

_type 是指向 _type 結構的指針,其中包含實際值的大小、種類、雜湊函數和字串表示等資訊。 data 是指向實際資料的指標。如果實際資料的大小小於或等於指標的大小,則將資料直接儲存到資料欄位中;否則,資料欄位將儲存指向實際資料的指標。
當特定類型的物件被賦值給interface{}類型的變數時,Go語言會隱式執行eface的裝箱操作,將_type欄位設定為值的類型,將data欄位設定為值的資料。例如,當執行語句 var i interface{} = 123 時,Go 會建立一個 eface 結構體,其中 _type 欄位代表 int 類型,data 欄位代表值 123。
當從interface{}中檢索儲存的值時,會發生一個拆箱過程,即類型斷言或類型判斷。此過程需要明確指定預期類型。如果interface{}中儲存的值的類型與預期類型匹配,則類型斷言將成功,並且可以檢索該值。否則,類型斷言將會失敗,這種情況需要額外的處理。

var i interface{} = "hello"
s, ok := i.(string)
if ok {
    fmt.Println(s) // Output "hello"
} else {
    fmt.Println("not a string")
}

可以看出,interface{}透過運行時的裝箱和拆箱操作,支援多種資料類型的操作。

3.2 泛型實現原理

Go核心團隊在評估Go泛型的實現方案時非常謹慎。共提交了三個實施方案:

  • 模板方案
  • 字典計畫
  • GC形狀模板方案

Stenciling方案也是C、Rust等語言實作泛型所採用的實作方案。其實現原理是,在編譯期間,根據呼叫泛型函數時的特定類型參數或約束中的類型元素,為每個類型參數產生泛型函數的單獨實現,以確保類型安全性和效能最優。然而,這種方法會減慢編譯速度。因為當呼叫多種資料類型時,泛型函數需要為每種資料類型產生獨立的函數,這可能會導致編譯後的檔案非常大。同時,由於CPU快取未命中、指令分支預測等問題,產生的程式碼可能無法有效率地運作。

Dictionaries 方案只為泛型函數產生一個函數邏輯,但加入了一個參數 dict 作為函數的第一個參數。 dict 參數在呼叫泛型函數時儲存類型參數的類型相關訊息,並在函數呼叫期間使用 AX 暫存器(AMD)傳遞字典資訊。這種方案的優點是減少了編譯階段的開銷,並且不會增加二進位檔案的大小。但增加了運行時開銷,無法在編譯階段進行函數最佳化,並且存在字典遞歸等問題。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type type struct {
    Size uintptr
    PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
    Hash uint32 // hash of type; avoids computation in hash tables
    TFlag TFlag // extra type information flags
    Align_ uint8 // alignment of variable with this type
    FieldAlign_ uint8 // alignment of struct field with this type
    Kind_ uint8 // enumeration for C
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    Equal func(unsafe.Pointer, unsafe.Pointer) bool
    // GCData stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, GCData is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    GCData *byte
    Str NameOff // string form
    PtrToThis TypeOff // type for pointer to this type, may be zero
}

Go 最終綜合了上述兩種方案,提出了 GC Shape Stenciling 方案進行通用實現。它以類型的 GC Shape 為單位產生函數代碼。具有相同 GC Shape 的類型重複使用相同的程式碼(類型的 GC Shape 指的是它在 Go 記憶體分配器/垃圾收集器中的表示)。所有指標類型都重複使用 *uint8 類型。對於具有相同 GC Shape 的類型,使用共享的實例化函數程式碼。此方案還會自動為每個實例化的函數程式碼新增一個dict參數,以區分具有相同GC Shape的不同類型。

var i interface{} = "hello"
s, ok := i.(string)
if ok {
    fmt.Println(s) // Output "hello"
} else {
    fmt.Println("not a string")
}

3.3 差異

從interface{}和泛型的底層實作原理可以發現,它們的主要差異是interface{}支援在執行時間處理不同的資料類型,而泛型支援在編譯階段靜態處理不同的資料類型。實際使用主要有以下區別:
(1) 效能差異:將不同類型的資料指派給介面{}或從介面{}擷取不同類型的資料時執行的裝箱和拆箱作業成本高昂,並會帶來額外的開銷。相較之下,泛型不需要裝箱和拆箱操作,並且泛型產生的程式碼針對特定類型進行了最佳化,避免了運行時效能開銷。
(2)型別安全:使用interface{}類型時,編譯器無法進行靜態型別檢查,只能在執行時進行型別斷言。因此,某些類型錯誤可能只能在運行時才能發現。相比之下,Go 的泛型程式碼是在編譯時產生的,因此泛型程式碼可以在編譯時獲取類型信息,保證類型安全。

4. 泛型的場景

4.1 適用場景

  • 實現通用資料結構時:透過使用泛型,您可以編寫一次程式碼並在不同的資料類型上重複使用它。這減少了程式碼重複並提高了程式碼的可維護性和可擴展性。
  • 在Go 中操作原生容器類型時:如果函數使用Go 內建容器類型(例如切片、映射或通道)的參數,且函數程式碼沒有對容器中的元素類型做出任何特定假設,使用泛型可以將容器演算法與容器中的元素類型完全解耦。在泛型語法出現之前,通常會使用反射來實現,但是反射使得程式碼可讀性較差,無法進行靜態類型檢查,大大增加了程式的運行時開銷。
  • 當不同資料類型的方法實現的邏輯相同時:當不同資料類型的方法功能邏輯相同,唯一差異是輸入參數的資料類型時,可以使用泛型來減少程式碼冗餘。

4.2 不適用場景

  • 不要用型別參數取代介面類型:介面支援某種意義上的泛型程式設計。如果對某些類型的變數的操作只呼叫該類型的方法,則直接使用介面類型即可,無需使用泛型。例如,io.Reader 使用介面從檔案和隨機數產生器讀取各種類型的資料。 io.Reader 從程式碼角度看很容易閱讀,效率很高,函數執行效率幾乎沒有差別,所以不需要使用型別參數。
  • 當不同資料類型的方法實作細節不同時:如果每種類型的方法實作不同,則應使用介面類型而不是泛型。
  • 執行時動態性較強的場景:例如使用switch進行型別判斷的場景,直接使用interface{}會有較好的效果。

5. 泛型中的陷阱

5.1 無比較

在Go語言中,類型參數是不允許與nil直接比較的,因為類型參數是在編譯時進行類型檢查的,而nil是運行時的一個特殊值。由於類型參數的底層類型在編譯時是未知的,因此編譯器無法確定類型參數的底層類型是否支援與 nil 進行比較。因此,為了維護類型安全並避免潛在的運行時錯誤,Go語言不允許類型參數與nil直接比較。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type type struct {
    Size uintptr
    PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
    Hash uint32 // hash of type; avoids computation in hash tables
    TFlag TFlag // extra type information flags
    Align_ uint8 // alignment of variable with this type
    FieldAlign_ uint8 // alignment of struct field with this type
    Kind_ uint8 // enumeration for C
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    Equal func(unsafe.Pointer, unsafe.Pointer) bool
    // GCData stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, GCData is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    GCData *byte
    Str NameOff // string form
    PtrToThis TypeOff // type for pointer to this type, may be zero
}

5.2 無效的底層元素

底層元素的類型T必須是基底類型,不能是介面類型。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
type type struct {
    Size uintptr
    PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
    Hash uint32 // hash of type; avoids computation in hash tables
    TFlag TFlag // extra type information flags
    Align_ uint8 // alignment of variable with this type
    FieldAlign_ uint8 // alignment of struct field with this type
    Kind_ uint8 // enumeration for C
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    Equal func(unsafe.Pointer, unsafe.Pointer) bool
    // GCData stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, GCData is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    GCData *byte
    Str NameOff // string form
    PtrToThis TypeOff // type for pointer to this type, may be zero
}

5.3 無效的聯合型別元素

聯合型別元素不能是型別參數,非介面元素必須成對不相交。如果有多個元素,則不能包含具有非空方法的介面類型,也不能進行比較或嵌入比較。

var i interface{} = "hello"
s, ok := i.(string)
if ok {
    fmt.Println(s) // Output "hello"
} else {
    fmt.Println("not a string")
}

5.4 介面類型不能遞歸嵌入

type Op interface{
       int|float 
}
func Add[T Op](m, n T) T { 
       return m + n
} 
// After generation =>
const dict = map[type] typeInfo{
       int : intInfo{
             newFunc,
             lessFucn,
             //......
        },
        float : floatInfo
} 
func Add(dict[T], m, n T) T{}

6. 最佳實踐

為了用好泛型,在使用過程中應注意以下幾點:

  1. 避免過度概括。 泛型並不適合所有場景,需要仔細考慮適合哪些場景。適當的時候可以使用反射:Go 有運行時反射。反射機制支援一定意義上的泛型程式設計。如果某些操作需要支援以下場景,可以考慮反射: (1) 對沒有方法的類型進行操作,其中介面類型不適用。 (2) 當各個類型的操作邏輯不同時,泛型不適用。一個例子是encoding/json套件的實作。由於不希望每個要編碼的類型實作 MarshalJson 方法,因此不能使用介面類型。並且由於不同類型的編碼邏輯不同,所以不應該使用泛型。
  2. 明確使用 *T、[]T 和 map[T1]T2,而不是讓 T 代表指標類型、切片或映射。 與 C 中的型別參數是佔位符並會替換為實際型別不同,Go 中的型別參數 T 的型別是型別參數本身。因此,將其表示為指標、切片、映射等資料類型,在使用過程中會導致許多意想不到的情況,如下所示:
type V interface{
        int|float|*int|*float
} 
func F[T V](m, n T) {}
// 1. Generate templates for regular types int/float
func F[go.shape.int_0](m, n int){} 
func F[go.shape.float_0](m, n int){}
// 2. Pointer types reuse the same template
func F[go.shape.*uint8_0](m, n int){}
// 3. Add dictionary passing during the call
const dict = map[type] typeInfo{
        int : intInfo{},
        float : floatInfo{}
} 
func F[go.shape.int_0](dict[int],m, n int){}

上面的程式碼會報錯:無效運算:ptr(受 *int | *uint 限制的 T 型別變數)的指標必須具有相同的基底型別。出現這個錯誤的原因是T是型別參數,而型別參數不是指針,不支援解引用操作。這可以透過將定義更改為以下內容來解決:

// Wrong example
func ZeroValue0[T any](v T) bool {
    return v == nil  
}
// Correct example 1
func Zero1[T any]() T {
    return *new(T) 
}
// Correct example 2
func Zero2[T any]() T {
    var t T
    return t 
}
// Correct example 3
func Zero3[T any]() (t T) {
    return 
}

概括

總的來說,仿製藥的好處可以概括為三個面向:

  1. 類型在編譯期間決定,確保型別安全。放進去的就是取出來的。
  2. 可讀性提高。實際的資料類型在編碼階段就已經明確知道。
  3. 泛型合併了針對相同類型的處理程式碼,提高了程式碼重複使用率,增加了程式的通用靈活性。 然而,泛型並不是一般資料類型所必需的。還是需要根據實際使用情況慎重考慮是否使用泛型。

Leapcell:Go Web 託管、非同步任務和 Redis 的高級平台

Go Generics: A Deep Dive

最後跟大家介紹最適合部署Go服務的平台Leapcell。

1. 多語言支持

  • 使用 JavaScript、Python、Go 或 Rust 進行開發。

2.免費部署無限個項目

  • 只需支付使用費用-無請求,不收費。

3. 無與倫比的成本效益

  • 即用即付,無閒置費用。
  • 範例:25 美元支援 694 萬個請求,平均回應時間為 60 毫秒。

4.簡化的開發者體驗

  • 直覺的使用者介面,輕鬆設定。
  • 完全自動化的 CI/CD 管道和 GitOps 整合。
  • 即時指標和日誌記錄以獲取可操作的見解。

5. 輕鬆的可擴充性和高效能

  • 自動擴充以輕鬆處理高並發。
  • 零營運開銷-只需專注於建置。

在文件中探索更多內容!

Leapcell Twitter:https://x.com/LeapcellHQ

以上是Go 泛型:深入探討的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Golang vs. Python:並發和多線程Golang vs. Python:並發和多線程Apr 17, 2025 am 12:20 AM

Golang更適合高並發任務,而Python在靈活性上更有優勢。 1.Golang通過goroutine和channel高效處理並發。 2.Python依賴threading和asyncio,受GIL影響,但提供多種並發方式。選擇應基於具體需求。

Golang和C:性能的權衡Golang和C:性能的權衡Apr 17, 2025 am 12:18 AM

Golang和C 在性能上的差異主要體現在內存管理、編譯優化和運行時效率等方面。 1)Golang的垃圾回收機制方便但可能影響性能,2)C 的手動內存管理和編譯器優化在遞歸計算中表現更為高效。

Golang vs. Python:申請和用例Golang vs. Python:申請和用例Apr 17, 2025 am 12:17 AM

selectgolangforhighpperformanceandcorrency,ifealforBackendServicesSandNetwork程序; selectpypypythonforrapiddevelopment,dataScience和machinelearningDuetoitsverserverserverserversator versator anderticality andextility andextentensivelibraries。

Golang vs. Python:主要差異和相似之處Golang vs. Python:主要差異和相似之處Apr 17, 2025 am 12:15 AM

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。Golang以其并发模型和高效性能著称,Python则以简洁语法和丰富库生态系统著称。

Golang vs. Python:易於使用和學習曲線Golang vs. Python:易於使用和學習曲線Apr 17, 2025 am 12:12 AM

Golang和Python分別在哪些方面更易用和學習曲線更平緩? Golang更適合高並發和高性能需求,學習曲線對有C語言背景的開發者較平緩。 Python更適合數據科學和快速原型設計,學習曲線對初學者非常平緩。

表演競賽:Golang vs.C表演競賽:Golang vs.CApr 16, 2025 am 12:07 AM

Golang和C 在性能競賽中的表現各有優勢:1)Golang適合高並發和快速開發,2)C 提供更高性能和細粒度控制。選擇應基於項目需求和團隊技術棧。

Golang vs.C:代碼示例和績效分析Golang vs.C:代碼示例和績效分析Apr 15, 2025 am 12:03 AM

Golang適合快速開發和並發編程,而C 更適合需要極致性能和底層控制的項目。 1)Golang的並發模型通過goroutine和channel簡化並發編程。 2)C 的模板編程提供泛型代碼和性能優化。 3)Golang的垃圾回收方便但可能影響性能,C 的內存管理複雜但控制精細。

Golang的影響:速度,效率和簡單性Golang的影響:速度,效率和簡單性Apr 14, 2025 am 12:11 AM

goimpactsdevelopmentpositationality throughspeed,效率和模擬性。 1)速度:gocompilesquicklyandrunseff,IdealforlargeProjects.2)效率:效率:ITScomprehenSevestAndardArdardArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdEcceSteral Depentencies,增強的Depleflovelmentimency.3)簡單性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具