我終于又開始使用 Go 語言編程了。雖然我在前兩年多的時間里積極參與這個項目馋艺,但從 2012 年起,我就基本沒有參加過這個項目迈套。最初捐祠,我之所以做出貢獻,是因為我是貝爾實驗室 Plan 9(操作系統(tǒng)) 和 FreeBSD 的粉絲桑李。我喜歡可用的踱蛀、基于 csp 的語言,但是 Go 最初的版本只能在 Linux 和 OS X 上運行贵白。那時候我只有 FreeBSD 系統(tǒng)率拒,因此,我將編譯器工具鏈禁荒、運行時和標準庫移植到 FreeBSD (有很多調試成果來自 Russ Cox )
然而猬膨,過去我的大部分工作都是在低延遲的系統(tǒng)軟件上,它們大部分是用 C 語言編寫的,自2007年起呛伴,我所有的雇主都不再支持 FreeBSD勃痴。因為我并沒有真正的機會用 Go 語言去編寫新的軟件,而且我最終也不再對維護一個操作系統(tǒng)的知識感興趣热康,我只是為了好玩沛申,所以我對 Go 語言的使用和貢獻都被擱置了。
現在我在谷歌工作姐军,我終于有機會用 Go 語言寫代碼了污它。雖然我仍然喜歡這門語言,但有一些經驗報告,例如風格那樣的東西結果阻止了我在過去的 5~6 年里使用這門語言,而我現在覺得有些麻煩衫贬。在一些同事的建議下德澈,我想我應該至少記錄下其中的一個。
我想念的一件事情是 C 語言 malloc
那樣的習慣用法固惯。在 C 語言中梆造,分配的內存通常是對 mallocs -family
函數的調用,它至少給您足夠的內存來完成您想要的功能葬毫,或者并不給您分配內存镇辉。慣用語法大致是這樣的:
T *p = malloc(sizeof *p);
注意,p (T *)
的類型只出現一次贴捡。這一行代碼利用了它的操作數時的大小忽肛,并且顯示了一個指針 —— 這其實是沒有發(fā)生的事情,因為操作符 sizeof
的結果必須①在編譯時是可識別的烂斋。這種編程語言而不是語法定義的結果是 sizeof
產生了指向對象的大小;它不會變成運行時的東西屹逛。C 語言的好處是,如果我改變用 T
表示的類型汛骂,我只改變聲明或定義中的類型罕模。在 site(s)
中不需要做任何更改,指針對象被分配給內存分配的結果帘瞭。上面的示例很簡單淑掌,讓我們考慮一個更復雜的情況,結構成員指向某種類型:
struct set {
size_t cap;
size_t nmemb;
int members[];
}
struct set *
set_create(size_t sz)
{
struct set *n = malloc(sizeof *n + (sz * sizeof *n->members));
if (n == NULL) {
return NULL;
}
n->members = malloc(sz * sizeof *n->members);
if (n->members == NULL) {
free(n);
return NULL;
}
n->cap = sz;
n->nmemb = 0;
return n;
}
如果以后我們想要更改 struct set
來支持除 int 以外的成員蝶念,我們可能會將成員更改為一個union抛腕,并添加一些 enum 類型來指定一些我們想要添加的字段。我們可以在不改變 set_create
中的任何代碼的情況下做到這一點媒殉。
每次我使用 Go 語言創(chuàng)建了一些結構類型,當需要嵌入一些像 slice 和 map 那樣需要分配內存的字段的時候都讓我很抓狂担敌。在 Go 中,我們被迫重復表達我們想要分配的東西的類型,盡管編譯器熟知這種類型而且類型推斷是符合語言習慣的(試想一下如這樣的表達式 a:= b
),我有時不得不深究一下嵌入字段的類型是什么。讓我們來看看在創(chuàng)建一個嵌入了 map 的結構體所涉及的內容:
type NamedMap struct {
name string
m map[string]string
}
func NewNamedMap(name string) *NamedMap {
return &NamedMap{name: name, m: map[string]string{}}
}
我們還可以在 NewNamedMap
中使用 make
适袜,但是仍然保留了return &NamedMap{name: name, m: make(map[string]string)}
— 再次柄错,重復它的類型。經過深思熟慮的代碼苦酱,應該只有一個(額外的)地方需要我們指定類型來分配它售貌,但是當類型改變時,這仍然需要多處改動代碼疫萤。當我在做原型的時候颂跨,這就會讓我抓狂,而且我還沒有充分考慮到我需要保存在 map 中的狀態(tài)扯饶。我發(fā)現在很多地方需要自己手動將 map[string]string
更改為 map[string]T
恒削,每次我需要更改多行代碼時池颈,它都會使我感到困擾。
有人可能會說钓丰,在寫代碼之前躯砰,我應該多考慮一下我需要什么,那樣會更好携丁。但我仍然會反駁說琢歇,在項目的生命周期中開發(fā)額外的狀態(tài)需求并不少見,比如在上面的例子中梦鉴。隨著時間的推移李茫,系統(tǒng)的約束也可能會發(fā)生變化,這樣一種最初非常好的類型最終會變得不可用肥橙。在 Go 中魄宏,set 結構可能是這樣的:
type Set struct {
nmemb int
cap int
members []int
}
func NewSet(sz int) *Set {
return &Set{cap: sz, members: []int{}}
}
你可能會問為什么我不只是用一個 slice,答案是這是一個演示這個問題的簡單例子存筏。不管怎樣宠互,我們以后可能想要支持不同類型的 slice胜卤,那樣我們又遇到了之前的問題耕陷。set 中如果有一個 slice,我們可能可以忽略初始化,假設我們在添加后只從 slice 中讀取藕溅。由于形如 map 和 channel 那樣的類型,因為我們必須在使用前進行分配继榆,所以會使得情況更加復雜巾表。那么在某個地方重復輸入信息并不罕見。
我不知道該如何解決這個問題略吨。對于復合文本集币,可能可以添加如下語法:
return &Set{cap: sz, members: Set.members{}}
如果你有一個指向 slice 的指針:
return &Set{cap: sz, members: &Set.members{}}
我不知道我是否喜歡這些復合文字語法。在 C 語言中翠忠,為支持 sizeof
行為而進行的修正感覺更有表現力和明顯:
return &Set{cap: sz, members: make(Set.members)}
但也許這只是我用 C 語言編程的時間太長了鞠苟。
我不知道改變新的有相似的行為是有用的還是有價值的;我懷疑它的使用是不尋常的。在任何情況下秽之,我都清楚這將減少重構軟件以及編寫新的軟件的開銷当娱。
這個問題并不是那么糟糕,但修復它肯定會讓我覺得更好考榨。在很多情況下跨细,Go已經比 C 和 C++ (我至今無法忍受) 更有表現力了。我認為河质,如果在語言中添加了對分配類型的推斷的支持冀惭,那么 Go 語言對 C 系統(tǒng)程序員來說就會更有吸引力震叙,因為除了 GC ,他們還有其他堅持的理由散休。(就我個人而言媒楼,我還希望看到一個關于支持系統(tǒng)級并發(fā)的更好的事情,但最好是單獨發(fā)布戚丸。)
我編輯了這篇文章匣砖,以修復 C 示例中的一個錯誤。當我最初編寫這個示例時昏滴,struct set
沒有使用靈活的數組成員猴鲫。Anmol Sethi 寫信詢問這個特性,并指出我錯誤地分配和再次分配給了FAM谣殊。我忘記了要刪除那些代碼拂共。
via: https://9vx.org/post/a-malloc-idiom-in-go/
作者:Devon
譯者:SergeyChang