SE-0244 Opaque Result Types 提案前一段時間通過了 review 並且在 Swift 5.1 裡完成了實現,我最早閱讀這份提案的時候理解不是很透徹,今天比較仔細地讀了這篇 Improving the UI of generics 之後有了更多的認識,而且發現自己之前發的 tweet 裡有一些錯誤的認知,所以這裡寫篇文章,希望用最直白的方式解釋清楚提案的內容,跟大家分享一下我自己的理解。
Opaque Result Types?
用最最簡單的一句話來介紹這個提案的內容,就是它能讓被呼叫者選擇泛型返回值的具體型別。
這是什麼意思呢?讓我們來看目前最常見的泛型函式宣告:
protocol Shape { ... }
func generic<T: Shape>() -> T { ... }
let x: Rectangle = generic() // type(of: x) == Rectangle, 呼叫方決定返回值型別
複製程式碼
這很好,但有時候我們不希望暴露出具體的返回型別,也不想讓呼叫者去依賴具體的型別,之前我們可以直接使用泛型型別:
func generic() -> Shape { ... }
let x = generic() // type(of: x) == Shape
複製程式碼
雖然這樣確實能達成我們的目的,但也會帶來一些副作用,例如說效能問題,因為實際上 generic
返回的是一個例項的容器。
我們更希望的是能夠像第一種宣告那樣,在編譯時就確定返回值的具體型別,並且由被呼叫方去決定:
func reverseGeneric() -> some Shape { return Rectangle(...) }
let x = reverseGeneric()
// type(of: x) == Rectangle
// 並且 x 的型別根據 reverseGeneric 的具體實現決定
複製程式碼
通過引入 some
這個關鍵字去修飾返回值,就可以讓被呼叫方選擇具體的返回值型別,並且是在編譯時確定下來的,這意味著我們不需要額外的容器去存放返回的實際值。
另外它還可以作為屬性使用:
func someNumber() -> some Numeric { ... }
var number: some Numeric = someNumber()
複製程式碼
它可以?
在這裡我們需要先確立一個條件,通過 some 修飾的型別,都會在編譯時確定下來,可以簡單地理解為被呼叫方負責傳入的一個泛型引數,相關的功能和限制都是基於這個特性延伸出來的。
根據前面的條件,如果返回值使用了 some
修飾,編譯器可以推匯出兩件很重要的事情:
- 同一個函式簽名,返回值的型別肯定也只能是同一個具體型別
- 外部不能夠依賴函式實現裡使用的返回值具體型別
這意味著什麼?在之前,兩個遵循了 Equatable
的例項不能判斷是否相等,因為我們並不知道它們具體的型別是否一樣,但如果使用了 some
,並且是由同一個函式返回的,那就完全不是問題了:
func randomNumber() -> some Equatable {
let i: Int = 32
return i
}
let x = randomNumber()
let y = randomNumber()
// 在這裡使用 == 是沒問題的
// 因為 x 跟 y 都是由 randomNumber 返回的
// 所以它們的具體型別必然一致
print(x == y)
複製程式碼
但是控制返回值型別的是呼叫方,所以你不能夠依賴呼叫方的具體實現:
func randomNumber() -> some Equatable {
let i: Int = 32
return i
}
var otherNumber: Int = 38
var x = randomNumber()
x = otherNumber // error
複製程式碼
雖然你知道 randomEquatableNumber
的實現裡使用的是 Int
,但具體的實現有可能隨時被調整,所以你不能依賴它。
結語
在這裡只是簡單地介紹了一些比較核心的內容,想了解更多細節的朋友可以看提案。實際上這個提案只是整個泛型語法改進計劃裡的第一步,之後等我有了更加深入的瞭解再做更多分享。
覺得文章還不錯的話可以關注一下我的部落格。