亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Go的“nil”類型的幕后發生了什么?

Go的“nil”類型的幕后發生了什么?

Go
慕森王 2022-08-24 10:35:45
我最近在一個流行的編程論壇上讀到,Go支持一些“無類型”值 - 特別是,Go的空值/底部類型。我對Go的經驗相當有限,在這個論壇上發表的一個聲明讓我措手不及 - 也就是說,用Go寫作是非法的。nilx := nil果然,帶有該行的玩具Go程序無法編譯,編譯器錯誤清楚地表明論壇中的注釋已簽出:不允許聲明和分配變量。這似乎是一個奇怪的限制,但事情變得更加奇怪。nilGo 中的一個常見習語是通過從函數返回元組來返回部分錯誤。像這樣:func might_error() (int, error) {    return 1, nil}func main() int {    x, err := might_error()    if err != nil {        panic("err was not nil!")    }    return x}據我所知,這至少有兩個不一致之處:首先,盡管在紙面上是無類型的,但它采用類型(通過實現與Go的鴨子類型相結合的接口),以符合 的返回類型簽名。nilerrorErrormight_error其次,它似乎被用于以前非法的聲明和分配,并且在(至少在可比較的語言中)可以被視為constexpr的情況下。nilmainmight_error更奇怪的是,用靜止的錯誤替換掉,使用未打字的nil!x, err := might_error()x, err := 1, nil我目前的想法是,在函數的類型簽名需要它的情況下,接口被注入到一個特定的實例中,這意味著它不再是非類型化的,而是在該特定實例的生命周期內成為類型化的,但我完全不確定這是正確的,因為它似乎是一個奇怪的設計選擇,因為它本質上不能清楚地推廣。Errornilnilnilnil是什么促使了這些設計選擇?為什么是非類型化而不是讓它成為一個正確的空類型,除非在方便的時候,在這一點上它成為一個實現接口的類型化值(據我所知)?nilError
查看完整描述

2 回答

?
猛跑小豬

TA貢獻1858條經驗 獲得超8個贊

這是一個完全沒有問題的問題,因為在實踐中,如果將一個nil值存儲在接口變量中,使該變量成為非nil,則可能會造成混淆。這是 https://golang.org/doc/faq#nil_error 每個人都會被這個問題咬幾次,直到你了解到包含nil變量的接口值本身不再是nil。這有點像只包含 nils 但本身是 non-nil 的。var s = []*int{nil, nil, nil}s


從技術上講(從語言設計的角度來看),您可以引入幾個“nils”,例如 用于指針、接口、函數和通道。(有點夸張)。有了這個,你可以有:nilnullnoopvac


type T whatever

func (t *T) Error() string // make *T implement error

var err error              // interface variable of type error

print(err == null)         // true, null is Zero-Value of interface types

print(err == nil)          // COMPILER ERROR, cannot compare interface to nil (which is for pointers only

var t *T = nil             // a nil pointer to T

err = t                    // store nil *T in err

print(err == null)         // false err is no longer null, contains t

您甚至可以刪除編譯器錯誤:


err = t                    // store nil *T in err

print(err == null)         // false, err contains t

print(err == nil)          // "deep" comparison yielding true

err = &T{}                 // store non-nil *T in err

print(err == null)         // still false

print(err == nil)          // false, value inside err is no longer nil

您還可以為 nil 定義一個默認類型,例如 這樣你就可以像編寫非類型常量 3.141 的默認類型是 float64 一樣進行編寫。但是,nil的這種默認類型將是任意的,根本沒有幫助(如我的示例所示; 不常見)。*[]chan*func()stringx := nilf := 3.141*[]chan*func()string


如果我沒記錯的話,在golang-nuts mailng列表中有一個關于這個主題的更長的討論,其中討論了這個設計的合理性。它歸結為:“具有多種含義而不是常量的實際現實生活問題是微小的(基本上只是包含nil的錯誤類型的變體不是nil)。這個小問題的“解決方案”將使語言復雜化(例如,通過為接口類型的零值引入一個文字)。告訴人們包含 nil 的接口值本身不再是 nil 可能比為接口類型引入類型化的 nils 或 null 更簡單。nilnull


在10多年的Go編程中,我實際上從未考慮過一個字面上被類型化或非類型化或常量或其他什么。您可能正在引用的文章正在構建純粹的學術性,但實際上在實踐中沒有問題,這是一個微小的設計決策,即指針,切片,映射,通道和函數類型的所有零值只有一個文本。nilnil


補遺


首先,即使 nil 在紙面上是無類型的,它也采用錯誤類型(通過實現 Error 接口和 Go 的 duck 類型),以便符合 might_error 的返回類型簽名。


這是對所發生事情的完全錯誤的描述。


如果你寫


func f() (r int) { return 7 }

則 7 被分配給 int 類型的 r,f 返回。這有效,因為可以將 7 分配給 int。



func might_error() (int, error) { return 1, nil }

同樣的情況是,error(接口類型)的第二個返回變量設置為 nil,因為 nil 可以分配給任何接口類型,就像您可以將 nil 分配給任何指針類型或任何函數類型一樣。


這與“實現錯誤接口與Go的鴨子類型”無關。絕對不是。nil 根本沒有實現錯誤接口。任何接口值都可以為零,就像可以是任何函數值、切片值或通道值一樣。將chan設置為nil基本上“清除”通道變量,這并不意味著nil以某種方式“實現通道接口”。您似乎將幾種類型的零值以及如何通過分配nil與實現接口來設置它們混為一談。所有這些基本上與是否鍵入nil無關。源代碼中的文字 os 重載,通常可以認為只是表示類型的零值。nil


查看完整回答
反對 回復 2022-08-24
?
慕哥6287543

TA貢獻1831條經驗 獲得超10個贊

你的例子不會編譯(因為不需要返回任何東西),當你進入一種語言的挑剔和細節領域時,你所有的例子都應該真正起作用。??main

我目前的想法是,在函數的類型簽名需要它的情況下,Error接口被注入到nil的特定實例中......

這不太對。Go 規范告訴我們這是如何工作的:

有三種方法可以從具有結果類型的函數返回值:

  1. 返回值可以在“return”語句中顯式列出。每個表達式都必須是單值表達式,并且可以分配給函數結果類型的相應元素。[截圖示例]

這是您在示例程序中使用的方法(我清理了一下以在Go Playground中編譯和運行)。表達式:

return 1, nil

方法:

unnamed_return_variable_1 = 1unnamed_return_variable_2 = nil

其中,兩個未命名的返回變量實際上沒有這些名稱(畢竟它們是命名的),但確實有類型,這要歸功于函數的聲明。所以

return 1, nil

根本不像:

x, err := 1, nil

但更像是:

var x int; var err error; x, err = 1, nil

如您所見,這也是非常有效的。

是什么促使了這些設計選擇?

為此,你必須問問設計師。

ThinkGoodly在評論中提到

未鍵入的 0 不會引起這種焦慮。

但是,非類型化常量具有默認類型0

非類型化常量具有默認類型,該類型是在需要類型化值的上下文中(例如,在短變量聲明(例如,沒有顯式類型)中將常量隱式轉換為的類型。非類型化常量的默認類型分別為 、 、 或,具體取決于它是布爾值、符文、整數、浮點數、復數還是字符串常量。i := 0boolruneintfloat64complex128string

預先聲明的標識符沒有類型,甚至沒有默認類型。它只是有一系列特殊規則,允許它在各個地方使用。nil

非語言規范的思考方式

雖然這不是它的定義方式(它是由Go規范定義的),但我建議這樣考慮這個問題:

  • 預先聲明的標識符1 是非類型化的。nil

  • 存在類型化的 nil 值。任何足夠像指針的東西都可以是 nil,也可以是 non-nil。例如,任何變量都可以是指向int-nil的指針,就像未初始化的變量一樣。此 nil 不是 nil “關鍵字”(預先聲明的標識符,見腳注);這是一個完全不同的“無”,就像布魯斯(班納)是一個與布魯斯(韋恩)完全不同的人。*int

  • 接口值由兩部分組成:類型和該類型的值。類型可以為零!這是另一種 nil,不同于 nil(未鍵入的“關鍵字”)和 nil(某些特定 pointer-y 類型的某些特定 nil)。如果類型為 nil,則該值也可以為 nil。如果兩者都為 nil,則接口值比較等于 <nil,nil>對,當需要與接口值進行比較時,未鍵入的“關鍵字”nil 會變成該對。如果任何一個為非零,則比較表明它們不相等。硬件實現是接口的兩個部分都是零:如果任何一個部分為非零,則整個事物為非零,因此“不是零”。

當上下文提供了足夠的信息時,將從 nil-the-“關鍵字”(預先聲明的標識符)到適當的硬件樣式零值進行轉換。分配給某個變量(甚至是未命名的變量)可提供上下文。分配給某些位置參數可提供上下文。嘗試使用簡短聲明無法提供上下文,因此會出現錯誤。


1 個一般來說,Go更喜歡預先聲明的標識符到關鍵字,因此等等實際上并不是關鍵字。這也排除了關鍵字集。盡管如此,有些東西——比如布爾常量和,或者預先聲明的標識符的神奇行為,只能通過不覆蓋預先聲明的標識符然后使用它來訪問。也就是說,如果您命名某些內容,則在該名稱超出范圍之前,您將無法獲得iota樣式功能。這些事情的行為方式通常通過其他語言的關鍵字來處理。如果你傾向于思考,比如說,C++或Java,你可能想不斷提醒自己,這些不是關鍵字,即使它們聞起來像它們。falsetruenilfalsetrueiotaiota


查看完整回答
反對 回復 2022-08-24
  • 2 回答
  • 0 關注
  • 162 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號