當我協助人們學習網頁開發時,一個超級常見的問題是,“為什麼我不能把這個函數傳入 `http.Handle` 方法中?它看起來與 `http.HandlerFunc` 是一模一樣的!”```gofunc demo(h http.Handler) {}func handler(w http.ResponseWriter, r *http.Request) {}func main() {// 這行代碼在編譯時間會報錯demo(handler)}```> 在 Go Playground 中運行範例程式碼 → https://play.golang.org/p/JAY4RDyQQn3我絕對能理解他們的困惑。對於一個接受 `http.HandlerFunc` 型別參數的函數來說,我們可以把顯式定義為 `http.HandlerFunc` 類型的變數傳入其中,我們也可以把有 `func(http.ResponseWriter, *http.Request)` 這種簽名的函數作為參數傳入,那為什麼上面的代碼不能運行?為了回答這個問題,我們需要深究下 `http.HandlerFunc` 和 `http.Handler` 類型,同時我們用一些範例程式碼來探求他們的特性。我們從 `http.HandlerFunc` 開始,它的定義是:```gotype HandlerFunc func(w ResponseWriter, r *Request)```於是,我們可以寫這樣的代碼:```gofunc demo(fn http.HandlerFunc) {}func handler(w http.ResponseWriter, r *http.Request) {}func main() {demo(handler)}```> 在 Go Playground 中運行範例程式碼 → https://play.golang.org/p/NdDbOhQPFQh注意,這段代碼與最上面的問題代碼是完全不同的 - 我們的 `demo` 函數 接收的參數類型是 `http.HandlerFunc` ,而不是 `http.Handler` 。還有 一個你可能都沒有發現的事實是,代碼中的 `handler` 其實並不是 一個 `http.HandlerFunc` ,它的實際類型的是 `func(http.ResponseWriter, *http.Request)` ,我們可以通過運行下面的測試代碼來揭示這個事實。```gopackage mainimport ("fmt""net/http")func main() {fmt.Printf("%T", handler)}func handler(w http.ResponseWriter, r *http.Request) {// handler func的實現內容}```> 在 Go Playground 中運行範例程式碼 → https://play.golang.org/p/ep8xlyKUODx所以,我們為啥會期待在最上面的問題代碼中能將 `handler` 作為參數 傳給 `dmeo` 函數呢?它的類型明顯都不一樣!當然,在我們的樣本中,我們的 `handler` 函數 完全符合 `http.HandlerFunc` 的定義,所以 Go 編譯器可以推斷我們是想把我們的變數轉為那個類型。也就是說,編譯器可以假裝我們的代碼是這樣寫的:```godemo(http.HandlerFunc(demo))```加上去的 `http.HandlerFunc` 部分是從 `demo` 函數中推匯出來的。好了,現在我們可以進行第二步了 -- `http.HandlerFunc` 也實現了 `http.Handler` 介面。為啥是這樣的,我們只需要看看兩者的源碼即可。```gotype HandlerFunc func(w ResponseWriter, r *Request)func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)}type Handler interface {ServeHTTP(ResponseWriter, *Request)}```請注意 `HandlerFunc` 有一個 `ServeHTTP` 方法匹配了 `Handler` 的介面聲明,這意味著 `HandlerFunc` 實現了 `Handler` 介面。這可能看起來很奇怪,但是在 Go 語言中這是完全有效。畢竟 `HandlerFunc` 是另外一種類型,而我們可以隨時往任意類型上增加方法。因為 `HandlerFunc` 實現了 `Handler` 介面,我們可以將 `HandlerFunc` 傳遞給接受 `Handler` 參數的函數中,只要參數類型是明確的被定義為 `HandlerFunc`。範例程式碼如下:```gopackage mainimport "net/http"func demo(fn http.Handler) {}func handler(w http.ResponseWriter, r *http.Request) {}func main() {demo(http.HandlerFunc(handler))}```> 在 Go Playground 中運行範例程式碼 → https://play.golang.org/p/K7sD51wnBL9現在,我們知道 `http.HandlerFunc` 實現了 `http.Handler` 介面,而且我們知道可以將類型為 `func(http.ResponseWriter, *http.Request)` 的對象傳遞給一個需要 `http.HandlerFunc` 型別參數的函數中,但是,為什麼我們不能傳遞 `func(http.ResponseWriter, *http.Request)` 類型給一個需要 `http.HandlerFunc` 型別參數的函數中?或者說,為什麼這樣的代碼不能通過編譯呢?```gofunc demo(h http.Handler) {}func handler(w http.ResponseWriter, r *http.Request) {}func main() {// 這行代碼在編譯時間會報錯demo(handler)}```簡單的回答是,編譯器不知道怎麼將 `func(http.ResponseWriter, *http.Request)` 類型轉換為 `http.Handler` 類型。為了實現這樣的轉換,編譯器需要知道我們想要將 `handler` 轉換為 `http.HandlerFunc`,但是在上面的代碼中,我們根本就沒有看到 `http.HandlerFunc` 這個類型。如果用我們之前說的推斷規則,Go 編譯器只有可能認為代碼其實是想要這樣寫的:```godemo(http.Handler(handler))```但是這顯然是不對的。`handler` 並沒有實現 `http.Handler` 介面,除非它轉換成實現了介面的 `http.HandlerFunc` 類型。還有一種辦法,我們做一個兩步轉換,將 `func(http.ResponseWriter, *http.Request)` 轉換成 http.Handler:``` 1 2func -> HandlerFunc -> Handler```如果我們顯式的將 `handler` 函數轉換為 `http.HandlerFunc`,那麼這個代碼是可以啟動並執行。```gofunc demo(h http.Handler) {}func handler(w http.ResponseWriter, r *http.Request) {}func main() {// 代碼可以編譯!demo(http.HandlerFunc(handler))}```> 在 Go Playground 中運行範例程式碼 → https://play.golang.org/p/Dm33TpgvONh再一次,如果我們假設 Go 編譯器能假裝我們的代碼是將傳入的參數自動轉換為實際希望的類型,代碼看起來像是這樣的:```godemo(http.Handler(http.HandlerFunc(handler)))```那麼,為什麼編譯器不學著自動為我們做這兩步簡單明了的類型轉換?對於初學者的解釋是,編譯器並不知道這兩步轉換就是你真正想要做的。讓我們假設你還有另外一個類型,假設它叫 `CowboyFunc`,它的定義如下:```gotype CowboyFunc func(w http.ResponseWriter, r *http.Request)func (f CowboyFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {s := time.Now()f(w, r)fmt.Println("Cowboy function duration:", time.Now().Sub(s))}```現在,如果我們調用 `demo(handler)` 就像我們之前做的那樣,然後編譯器自動將 `handler` 類型轉換為了 `http.HandlerFunc` 類型,然後編譯順利完成,但是這是開發人員真正想要做的嗎?萬一開發人員是想將 `handler` 轉換成 `CowboyFunc` 呢?它也實現了 `http.Handler` 介面。```godemo(CowboyFunc(handler))```> 在 Go Playground 運行範例程式碼 → https://play.golang.org/p/tJELwdEThyx你可以看到這也是有效代碼,這意味著編譯器並不知道我們到底是要這兩者的哪一個,除非我們清楚明白的告訴它。總而言之,你不能直接將 `func(http.ResponseWriter, *http.Request)` 傳給 需要 `http.Handler` 型別參數的函數中,這也許讓你覺得疑惑或者很繁瑣,但是不能這麼做是有合理的原因的,而一旦你瞭解了這些原因,你可能更感謝那些時不時就出現的,讓人討厭的錯誤了。
via: https://www.calhoun.io/why-cant-i-pass-this-function-as-an-http-handler/
作者:Jon Calhoun 譯者:MoodWu 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
230 次點擊