我們擅長商業(yè)策略與用戶體驗的完美結(jié)合。
歡迎瀏覽我們的案例。

對 Go+ 來說,“面向數(shù)據(jù)科學(xué)” 這個目標注定有非常長遠的路要走。所以去年 Go+ 的版本迭代主要精力都花在了 “低門檻” 上。我們努力讓 Go+ 的使用門檻低到和 Python 相當?shù)乃?。這是從 Go+ 作為 “面向 STEM 教育”,作為一種中小學(xué)生就能夠?qū)W習(xí)和掌握的教學(xué)語言而做出的努力。我認為這些努力對 Go+ 的發(fā)展來說非常重要。2022 年我們?nèi)匀粫^續(xù)去加強 Go+ 在低門檻方向上的工作。
但是在去年年底的時候,我們將 Go+ 下一個里程碑(v1.2)的版本計劃做了非常巨大的調(diào)整:我們從之前的一籮筐的 Go+ 生態(tài)發(fā)展的計劃,調(diào)整成為就干一個非常硬的硬骨頭:實現(xiàn)對 C 語言的完美支持。
為什么需要做這個改變?
因為到目前為止,Go+ 從 “面向工程” 這個目標來說,它只是 “更好的 Go”??陀^來講,這個 “更好” 并不足以讓軟件工程師們心動到改變自己的習(xí)慣,采用 Go+ 來進行日常的開發(fā)工作。這個 Why Go+ 如果回答不好,那么 Go+ 的面向工程這個目標就僅僅停留于口號。
Go 語言足夠好,在它擅長的服務(wù)端開發(fā)領(lǐng)域上,你甚至幾乎可以肯定地說它是最好的。但是在其他所有領(lǐng)域,無論是 PC、Mobile、Web、小程序、嵌入式、區(qū)塊鏈與 Web3、大數(shù)據(jù)與 AI、編程教學(xué)領(lǐng)域等等,Go 都顯得不夠好。如果用學(xué)生來打比方,Go 像是一個偏科偏的非常嚴重的學(xué)生,有一門學(xué)科特別擅長考 99 分,但是其它學(xué)科都只是勉強的及格線。
是什么制約了 Go 語言在其他領(lǐng)域的發(fā)展?
我們以 Web3 為例。這很可能是與服務(wù)端開發(fā)最為接近,Go 最容易取得壓倒性優(yōu)勢的領(lǐng)域。但是實際的戰(zhàn)績?nèi)绾文?我花時間對幾十個頭部的 Web3 項目進行了分析,最后的統(tǒng)計結(jié)果很意外:在這個領(lǐng)域 Go 語言的確采用率很高的確沒錯,但是有另一個語言 Rust 的采用率也很高,兩者的采用率基本上接近 1:1,甚至一些項目同時采用 Go 和 Rust。
為什么會這樣?在正統(tǒng)的服務(wù)端開發(fā)中 Rust 的采用率可能連 Go 的 1/10 都沒有,為什么一個看起來完全類似的同樣都是網(wǎng)絡(luò)應(yīng)用類的場景,其采用率居然有這么大的差別?
我知道一些人把這個事情歸因到 Go 是 GC 語言,Rust 性能更好這一點上。但我認為這并不成立。如果對網(wǎng)絡(luò)服務(wù)性能是重要的,那么在服務(wù)端開發(fā)(尤其是云計算)這樣一個成本很敏感的領(lǐng)域,Rust 應(yīng)該比 Go 更有市場競爭力才對。
我認為最有可能的關(guān)鍵因素是兩個:
語言帶給人的安全感(更少的安全漏洞);
C 語言的兼容性。
Rust 的確能夠帶來更好的安全感(至少表面上看是這樣),但這個因素能夠影響到底有多少不得而知。但我認為 C 語言的兼容性是更為重要的因素。Web3 是一個快賽道,發(fā)展日新月異,大家都會搶時間。從搶時間這個角度來看,只對比語言角度 Go 比 Rust 有優(yōu)勢得多。是什么原因讓大家覺得用 Rust 開發(fā)起來更快?
因為 Rust 能夠讓軟件工程師們復(fù)用大量的 C 語言的社區(qū)資源。編程語言史發(fā)展到今天,哪門語言資源最多我想不用我在這里多費口舌,對于 Web3 這樣一個日新月異的新領(lǐng)域來說,能夠復(fù)用既有 C 語言的資源,可以輕易打敗 Go 僅僅靠語言特性本身形成的便捷性優(yōu)勢。
簡單一句話,cgo 太雞肋,與 C 語言的兼容上,Go 也就是做到了聊勝于無而已。
這里 Web3 只是一個例子。無論進入到任何服務(wù)端之外的新領(lǐng)域,對 Go 來說,兼容 C 都是至關(guān)重要,沒有之一。
想清楚了這一點,Go+ 面向工程領(lǐng)域的第一個執(zhí)行目標就出來了:實現(xiàn)對 C 語言的完美兼容。要么讓 cgo 變好,要么提供一個遠超 cgo 的新的兼容 C 語言的方案。
這就是去年年底,為什么我們調(diào)整 Go+ v1.2 版本的迭代目標的原因。
目標有了,但是怎么做到呢?剛剛過去的這個春節(jié)里我?guī)缀趺刻煲挥锌站驮谒伎歼@個問題。為此我查閱了大量的 github 上的代碼,基本上圍繞著 c2go、c2goasm、cgo、binary/asm2go、bytecode/vm2go 這些話題。
最終,我沒有選擇優(yōu)化 cgo,而是選擇了:c2go。簡單說,就是把 C 代碼轉(zhuǎn)換為 Go 代碼,然后重新用 Go 編譯器進行編譯。
當我把要做 c2go 這個想法發(fā)到 Go+ 貢獻者群的時候,大家第一反應(yīng)都是覺得不可能,這太難了。
這條路并非沒有人走過。
大家都知道,Go 團隊自己就做過這事,當然他們的目標并不是做一個通用版本的 c2go,而是要實現(xiàn) Go 的自舉,把 Go 編譯器(也包括運行時等)所有的 C 代碼都轉(zhuǎn)成 Go。所以它的代碼可以針對 Go 編譯器寫特殊的轉(zhuǎn)換規(guī)則,只要滿足不別人肉去改編譯器就好。
第三方c2go 這個項目。它從 2017 年 2 月開始做,到今天有 5 個年頭了,最初作者雄心勃勃,給自己定了一個小目標:在沒有任何人工干預(yù)的情況下實現(xiàn) sqlite3 到 Go 代碼的自動轉(zhuǎn)換。然而這個目標至今沒有實現(xiàn)。
但是我認為它的選擇的大方向思路是對的。這個 c2go 項目把整個轉(zhuǎn)換過程分為這樣幾個步驟:
首先,用 C 語言的預(yù)處理程序(preprocessor)解決掉宏和各種預(yù)編譯指令。這樣,我們就只需要處理最純粹的 C 代碼。C 語言的 spec 非常的精簡,比 Go 語言的 spec 還要短小很多,并且每個語法的功能與 Go 有足夠強的相似性,這意味著這一步下來后,我們工作量相對可控許多。
這一步是重要的,它意味著平臺相關(guān)的工作都在這一步干了,我們后續(xù)的步驟不用考慮跨平臺。每個平臺預(yù)處理結(jié)果產(chǎn)生的 C 代碼可以不同,從而轉(zhuǎn)換生成的 Go 代碼也不同,這是可接受的。
其次,生成的 C 代碼再通過 llvm project 中的 clang 命令行進行解析(parser):
clang -Xclang -ast-dump=json -fsyntax-only [C源文件...]
這樣就得到 C 語言的抽象語法樹(C AST)。這一步自己做也可以,但是既然有現(xiàn)成的,能夠先省心就先省心吧,以后有空了再改寫不遲(注意 github.com/elliotchance/c2go 這個項目用的是 -ast-dump 開關(guān),而不是 -ast-dump=json,這樣就不得不在再寫一個額外的文本解析代碼來解析 clang 的輸出)。
接著,就是將 C AST 轉(zhuǎn)為 Go AST。這一步最為核心,我們的大部分工作都集中在這里。類似的工作我們在 Go+ 中也已經(jīng)做過了,只不過我們之前做的是 Go+ AST 轉(zhuǎn)為 Go AST 而已??紤] C 語言的 spec 比 Go+ 語言 spec 要小很多,我們心里對工作量就有了大體的估計。
這一步會有少量的技術(shù)難點,比如 C 的指針是可以移動的,比如 C 語言有 union 數(shù)據(jù)類型。但總體來說這些語法都不是特別復(fù)雜,工作量是可控的。
C 是手工管理內(nèi)存的,這點在轉(zhuǎn)為 Go 后可以仍然保持不變。C 語言的 malloc 函數(shù)可以從 Go 進程里面劃出一片內(nèi)存來進行手工管理。
還有一些 corner case,比如在 C 代碼里面插入?yún)R編指令的。我們可以考慮引入一個通用的過濾機制來解決:在 c2go 轉(zhuǎn)換過程的配置文件中,指定哪些函數(shù)或 C 源文件應(yīng)該被忽略,然后在該工程中加入一個手工實現(xiàn)的 Go 源文件來自己實現(xiàn)這些被忽略的函數(shù)即可。
在介紹 Go+ 編譯器實現(xiàn)原理(請移步 bilibili 搜索 "Go+ 公開課 · 第1期|Go+ v1.x 的設(shè)計與實現(xiàn)" 進行查看)的時候,我提到過 github.com/goplus/gox 這個項目,它是用來輔助生成 Go AST 的模塊。在 C AST 轉(zhuǎn)為 Go AST 中,我們也會借助它來大幅減少開發(fā)工作量。
生成了 Go AST,剩下來的工作就和我們在 Go+ 一樣了,通過 Go AST 調(diào)用 go/format 這個標準庫來生成 Go 源文件,最后用 Go 編譯器編譯它。
在我解釋以上 c2go 的全過程后,小伙伴們開始相信這條路走得通。
當然,在我內(nèi)心中,還有一個更為重要的理由:我們有機會干得比 cgo 更好。如果選擇優(yōu)化 cgo 的話,是達不到這個目標的,我們唯一能夠做的只是讓 cgo 更快。
如何讓 Go+ 對 C 語言的支持更好?
在 Go+ 與 C 語言的互操作性上,我有以下這些核心的考量準則:
其一,C 代碼不需要經(jīng)過額外包裝,可以直接由 Go+ 來調(diào)用。以 Hello world 這個經(jīng)典程序為例,Go+ 調(diào)用 C 標準庫的 printf 代碼如下:
import "C"
C.printf(C"Hello, world\n")
它挺像 cgo,但是它并不是。你可以拿它和cgo 的代碼對比一下:
package main
/*
#include
#include
*/
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello World\n")
defer C.free(unsafe.Pointer(cstr))
C.printf(cstr)
}
可以看到,在 Go 語言中 import "C" 并不是真導(dǎo)入一個包,而只是說這里有 C 代碼被插入。相比 Go 語言,Go+ 對 C 的支持更像是把 C 看做 Go+ 原生代碼的一部分,而不是外來者。具體體現(xiàn)在:
不用寫任何 C 代碼就可以引用。C 實現(xiàn)的模塊如同 Go 實現(xiàn)的模塊一樣,就是正常的一個 package 而已。import "C" 表示導(dǎo)入 C 語言的標準庫(當然不同平臺不同 C 編譯器的 C 標準庫并不完全相同,這個考慮在 gop.mod 中對 C 庫這個模塊進行版本綁定)。
引入了 C 的字符串常量語法 C"...",以增強 C 函數(shù)使用上的便捷性。代碼中的 C"Hello, world\n" 等價于 []int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0'},是一個以 '\0' 為結(jié)尾的 C 字符串。
那么對于非 C 標準庫,比如類似 sqlite3 這樣的知名 C 庫,怎么導(dǎo)入?我們設(shè)想是這樣的:
import "C/sqlite3"
sqlite3.XXX(...)
那么如果是用戶自己實現(xiàn)的 C 庫呢?嗯,最通用的形式是這樣的:
import "C/github.com/foo/bar"
bar.XXX(...)
很簡潔,和 Go 語言寫的庫看起來沒啥大區(qū)別,對吧?
其二,C 與 Go 的類型系統(tǒng)盡可能一致,以降低互操作的成本。在 C 語言中的 void (*)() 到 Go 里面就是 func(),兩者屬于同一個 type。其他還有 C 語言的 int 類型也是如此。不考慮引入 C.int 這類數(shù)據(jù)結(jié)構(gòu),它就等同于 Go 語言的 int。為此 Go+ 還會引入內(nèi)建的 char 類型,實際上它只是 int8 的別名。
至于要不要引入 union,我估計大概率 Go+ 不支持 union 概念。但是 C 代碼中定義的 union 類型,在 Go+ 中可以識別。比如 sqlite3.XXX 如果是一個 union 類型,那就會有正常的 union 行為,包括可以正確訪問其中的成員變量。但是我們不允許在 Go+ 源文件中顯式定義一個 union 類型。
在 C 語言中,函數(shù)會有調(diào)用約定(Call Convention)的概念。最常見的調(diào)用約定有 cdecl 和 stdcall。其中,cdecl 是 C 函數(shù)默認的調(diào)用約定,它的好處是支持類似 printf 這種允許有不定參數(shù)的函數(shù)。而 stdcall 是 Windows API 的調(diào)用約定,這個調(diào)用方式有時候會被叫做 pascal,這當然是因為它與 Pascal 語言的函數(shù)調(diào)用約定一致的原因。
這種函數(shù)的調(diào)用約定的差異在翻譯成 Go 代碼后會被取消。也就是說,void (cdecl *)() 和 void (stdcall *)() 這兩種函數(shù)指針類型在 C 語言里面是兩個完全不同的類型并且無法相互轉(zhuǎn)換(強制轉(zhuǎn)換后進行函數(shù)調(diào)用會導(dǎo)致程序崩潰),但是翻譯成 Go 后就都是 func(),沒有區(qū)別。
其三,翻譯成 Go 代碼后的程序語義,也就是 C 程序員對 C 形成的常規(guī)的語義理解都仍然正確。包括:C 字符串以 '\0' 為結(jié)尾,C 是手工管理內(nèi)存的等等,這些都仍然不變。如上面已經(jīng)提到過的那樣,在實現(xiàn)上 C.malloc 會從 Go 進程中劃出一片內(nèi)存來進行手工管理。
最后總結(jié)就一句話:我對這個 c2go 項目相當認真。目前它已經(jīng)放到 github 上 goplus 這個組織里面了。
我覺得這事對 Go 未來十年的生態(tài)繁榮是至關(guān)重要的一項工作,否則 Go 就真的只能偏安在服務(wù)端開發(fā)領(lǐng)域了。
當然從 Go+ 的角度來說,它是我們在 “面向工程” 領(lǐng)域的目標下,給軟件工程師們交出的最重要的一份答卷。
?。?a href="http://m.rzslsm.com/wechat/">邯鄲小程序開發(fā))


小米應(yīng)用商店發(fā)布消息稱 持續(xù)開展“APP 侵害用戶權(quán)益治理”系列行動 11:37:04
騰訊云與CSIG成立政企業(yè)務(wù)線 加速數(shù)字技術(shù)在實體經(jīng)濟中的落地和應(yīng)用 11:34:49
樂視回應(yīng)還有400多人 期待新的朋友加入 11:29:25
亞馬遜表示 公司正在將其智能購物車擴展到馬薩諸塞州的一家全食店 10:18:04
三星在元宇宙平臺推出游戲 玩家可收集原材料制作三星產(chǎn)品 09:57:29
特斯拉加州San Mateo裁減229名員工 永久關(guān)閉該地區(qū)分公司 09:53:13