?
楔子
?
所有的編程語言都致力于將重復的任務簡單化,并為此提供各種各樣的工具。在 Rust 中,泛型(generics)就是這樣一種工具,它是具體類型或其它屬性的抽象替代。在編寫代碼時,我們可以直接描述泛型的行為,以及與其它泛型產生的聯系,而無須知曉它在編譯和運行代碼時采用的具體類型。
總結一下泛型就是,提高代碼的復用能力,處理重復代碼。泛型是具體類型或者其它屬性的抽象代替,編寫的泛型代碼不是最終的代碼,而是一些模板,里面有一些占位符,編譯器在編譯的時候會將占位符替換為具體的類型。
?
函數中的泛型
?
函數中定義泛型的時候,我們需要將泛型定義在函數的簽名中:
?
//?這種定義方式是錯誤的,因為?T?不在作用域中 //?我們要將其放在簽名里面 fn?func(arg:?T)?->?T?{ ????arg? } //?這樣做是正確的 fn?func(arg:?T)?->?T?{ ????arg }
?
里面的 T 就是一個泛型,它可以代表任意的類型,然后在編譯的時候會將其替換成具體的類型,這個過程叫做單態化。
另外這個 T 就是一個占位符,你換成別的也可以,只是我們一般寫作 T。
這里我們連續聲明了多個變量 x,這在 Rust 里面是沒有問題的,因為 Rust 有一個變量隱藏機制。然后再來看一下變量 x 的類型,雖然泛型 T 可以代表任意類型,但 Rust 在編譯的時候會執行單態化,確定泛型的具體類型。
比如傳一個 123,那么 T 就會被標記為 i32,因此返回的也是 i32,至于其它類型同理。還是那句話,T 只是一個占位符,至于它到底代表什么類型,取決于我們調用時傳遞的值是什么類型。
比如傳遞一個?&str,那么函數就會被 Rust 替換成如下:
?
fn?func(arg:?&str)?->?&str?{ ????arg }
?
以上過程被稱為單態化,Rust 在編譯期間會將泛型 T 替換成具體的類型。因此如果想使用泛型,那么函數簽名中的泛型一定要出現在函數參數中,然后根據調用方傳遞的值的類型,來確定泛型。
總結一下:泛型一定要在函數的簽名中,也就是在函數后面通過 <>?進行指定,否則的話泛型是無法使用的。此外,泛型還要出現在參數中,這是毫無疑問的,不然定義泛型干啥。
當然啦,泛型不止可以定義一個,定義任意個都是可以的。
?
//?如果有多個泛型,那么在?<>?里面通過逗號分隔 //?然后要保證函數簽名?<>?里面聲明的泛型, //?都要在函數參數中出現,也就是要將定義的泛型全用上 fn?func( ????arg1:?A,?arg2:?B,?arg3:?C )?->?(C,?A)?{ ????(arg3,?arg1) } fn?main()?{ ????//?函數?func?定義了三個泛型,然后返回的類型是?(C,?A) ????//?這里傳遞三個參數,顯然當調用時,Rust?會確定泛型代表的類型 ????//?A?是?i32,B?是?f64,C?是?&str ????let?x?=?func(123,?3.14,?"你好"); ????//?泛型可以接收任何類型,那么當調用時 ????//?A?是?Vec,B?是?[i32;2],C?是?(i32,?i32) ????let?y?=?func(vec![1,?2],?[1,?2],?(3,?4)); }
?
這里我們定義了三個泛型,然后返回的類型是 (C, A)。而 Rust 會根據參數的類型,來確定泛型,所以變量 x 是 (&str, i32) 類型,變量 y 是?((i32,?i32), Vec
事實上 IDE 也已經推斷出來了,總的來說泛型應該不難理解。
?
結構體中的泛型
?
如果一個結構體成員的類型不確定,那么也可以定義為泛型。
?
struct?Point?{ ????x:?T, ????y:?T }
?
和函數一樣,泛型一定要寫在 <> 當中作為簽名出現,然后才可以使用,相當于告訴 Rust 都定義了哪些泛型。然后簽名中的泛型,一定要全部使用,會根據函數調用時給參數傳的值、或者實例化結構體時給成員傳的值,來確定泛型代表哪一種類型。
如果簽名中的泛型沒有全部使用,那么 Rust 就無法執行單態化,于是報錯。所以泛型一定要全部使用,再說了,不使用的話,定義它干嘛。
?
struct?Point?{ ????x:?T, ????y:?T } fn?main()?{ ????let?p1?=?Point{x:?11,?y:?22}; ????let?p2?=?Point{x:?11.1,?y:?22.2}; }
?
T 只是一個占位符,具體什么類型要由我們傳遞的內容決定,可以是 i32,可以是 f64。但由于成員 x 和 y 的類型都是 T,所以它們的類型一定是一樣的,要是 i32 則都是 i32,要是 f64 則都是 f64。
如果希望類型不同,那么只需要兩個泛型即可。
?
struct?Point?{ ????x:?T, ????y:?U } fn?main()?{ ????//?x?和?y?的類型可以相同,也可以不同 ????//?因為它們都可以接收任意類型 ????let?p1?=?Point{x:?11,?y:?22}; ????let?p2?=?Point{x:?11.1,?y:?22.2}; ????let?p3?=?Point{x:?"11.1",?y:?String::from("satori")}; ????let?p3?=?Point{x:?vec![1,?2,?3],?y:?(1,?2,?3)}; }
?
還是那句話,泛型可以接收任意類型,想傳啥都行,具體根據我們傳遞的值來確定。
?
枚舉中的泛型
?
枚舉也是支持泛型的,比如之前使用的 Option
?
enum?Option?{ ????Some(T), ????None }
?
里面的 T 可以代表任意類型,然后我們再來自定義一個枚舉。
?
//?簽名中的泛型參數必須都要使用 //?比如函數簽名的泛型,要全部體現在參數中 //?枚舉和結構體簽名的泛型,要全部體現在成員中 enum?MyOption?{ ????//?這里?A、B、C?都是我們隨便定義的,可以代指任意類型 ????//?具體是哪種類型,則看我們傳遞了什么 ????Some1(A), ????Some2(B), ????Some3(C), } fn?main()?{ ????//?泛型不影響效率,是因為?Rust?要進行單態化 ????//?所以泛型究竟代表哪一種類型要提前確定好 ????//?這里必須要顯式指定?x?的類型。枚舉和結構體不同 ????//?結構體每個成員都要賦值,所以?Rust?能夠基于賦的值推斷出所有的泛型 ????//?但枚舉的話,每次只會用到里面的一個成員 ????//?如果還有其它泛型,那么?Rust?就無法推斷了 ????//?比如這里只能推斷出泛型?C?代表的類型,而?A?和?B?就無法推斷了 ????//?因此每個泛型代表什么類型,需要我們手動指定好 ????let?x:?MyOption?=?MyOption::Some3(123); ????match?x?{ ????????MyOption::Some1(v)?=>?println!("我是?i32"), ????????MyOption::Some2(v)?=>?println!("我是?f64"), ????????MyOption::Some3(v)?=>?println!("我是?u8"), ????} ????//?泛型可以代表任意類型,指定啥都是可以的 ????let?y:?MyOption ?= ????????MyOption::Some3(String::from("xxx")); ????match?y?{ ????????MyOption::Some1(v)?=>?println!("我是?u8"), ????????MyOption::Some2(v)?=>?println!("我是?i32"), ????????MyOption::Some3(v)?=>?println!("我是?String"), ????} ???? ????/* ????我是?u8 ????我是?String ????*/ }
?
如果覺得上面的例子不好理解的話,那么再舉個簡單的例子:
?
enum?MyOption?{ ????MySome1(T), ????MySome2(i32), ????MySome3(T), ????MyNone } fn?main()?{ ????//?這里我們沒有指定?x?的類型 ????//?這是因為?MyOption?只有一個泛型 ????//?通過給?MySome1?傳遞的值,可以推斷出?T?的類型 ????let?x?=?MyOption::MySome1(123); ????//?同樣的道理,Rust?可以自動推斷,得出?T?是?&str ????let?x?=?MyOption::MySome3("123"); ????//?但此處就無法自動推斷了,因為賦值的是?MySome2?成員 ????//?此時?Rust?獲取不到任何有關?T?的信息,無法執行推斷 ????//?因此我們需要手動指定類型,但仔細觀察一下聲明 ????//?首先,如果沒有泛型的話,那么直接?let?x:?MyOption?=?...?即可 ????//?但里面有泛型,所以此時除了類型之外,還要連同泛型一起指定 ????//?也就是?MyOption ????let?x:?MyOption ?=?MyOption::MySome2(123); ????//?當然泛型可以代表任意類型,此時的?T?則是一個?Vec ?類型 ????let?x:?MyOption >?=?MyOption::MySome2(123); }
?
所以一定要注意:在聲明變量的時候,如果 Rust 不能根據我們賦的值推斷出泛型代表的類型,那么我們必須要手動聲明類型,來告訴 Rust 泛型的相關信息,這樣才可以執行單態化。
對于結構體也是同樣的道理:
?
struct?Girl1?{ ????field:?i32, } struct?Girl2?{ ????field:?T, } fn?main()?{ ????//?下面兩個語句類似,只是第二次聲明?g1?的時候多指定了類型 ????let?g1?=?Girl1?{?field:?123?}; ????let?g1:?Girl1?=?Girl1?{?field:?123?}; ????//?下面兩條語句也是類似的,第二次聲明?g2?的時候多指定了類型 ????//?但此時的類型有些不一樣,Girl2?的結尾多了一個? ????//?原因很簡單,因為?Girl2?里面有泛型 ????//?所以在顯式指定類型的時候,還要將泛型代表的類型一塊指定,否則報錯 ????let?g2?=?Girl2?{?field:?123?}; ????let?g2:?Girl2 ?=?Girl2?{?field:?123?}; }
?
然后還有一點比較重要,就是在聲明的時候,只需在 <> 里面指定泛型即可,什么意思呢?舉個例子:
?
struct?Girl?{ ????field1:?String, ????field2:?T, ????field3:?W, ????field4:?E, ????field5:?i32, } fn?main()?{ ????//?這里可以不指定類型,因為?Rust?可以推斷出來 ????//?不過這里我們就顯式指定。而雖然 Girl 有 5 個成員 ????//?但泛型的數量是三個,因此聲明變量的時候也要指定三個 ????//?由于定義結構體的時候,泛型順序是?E?T?W ????//?所以這里的?f64?就是?E,u8?就是?T,Vec ?就是?W ????let?g:?Girl >?=?Girl?{ ????????field1:?String::from("hello"), ????????field2:?123u8, ????????field3:?vec![1,?2,?3], ????????field4:?3.14, ????????field5:?666, ????}; }
?
以上就是在枚舉中使用泛型,并且針對泛型的用法稍微多啰嗦了一些。
?
方法中的泛型
?
我們也可以對方法實現泛型,舉個例子:
?
struct?Point?{ ????x:?T, ????y:?U } //?針對?i32、f64?實現的方法 //?只有傳遞的?T、U?對應?i32、f64?才可以調用 impl?Point ?{ ????fn?m1(&self)?{ ????????println!("我是?m1?方法") ????} } fn?main()?{ ????let?p1?=?Point{x:?123,?y:?3.14}; ????p1.m1();??//?我是?m1?方法 ????let?p2?=?Point{x:?3.14,?y:?123}; ????//p2.m1(); ????//調用失敗,因為?T?和?U?不是?i32、f64,而是?f64、i32 ????//所以?p2?無法調用?m1?方法 }
?
可能有人好奇了,聲明方法的時候不考慮泛型可不可以,也就是 impl Point {}。答案是不可以,如果結構體中有泛型,那么聲明方法的時候必須指定。但這就產生了一個問題,那就是只有指定類型的結構體才能調用方法。
比如上述代碼,只有當 x 和 y 分別為 i32、f64 時,才可以調用方法,如果我希望所有的結構體實例都可以調用呢?
?
struct?Point?{ ????x:?T, ????y:?U } //?針對?K、f64?實現的方法,由于?K?是一個泛型 //?所以它可以代表任何類型(泛型只是一個符號) //?因此不管?T?最終是什么類型,i32?也好、&str?也罷 //?K?都可以接收,只要?U?是?f64?即可 //?然后注意:如果聲明方法時結構體后面指定了泛型 //?那么必須將使用的泛型在?impl?后面聲明 impl? ?Point ?{ ????fn?m1(&self)?{ ????????println!("我是?m1?方法") ????} } //?此時?K?和?S?都是泛型,那么此時對結構體就沒有要求了 //?因為不管?T?和?W?代表什么,K?和?S?都能表示,因為它們都是泛型 impl? ?Point ?{ ????fn?m2(&self)?{ ????????println!("我是?m2?方法") ????} } //?這里我們沒有使用泛型,所以也就無需在?impl?后面聲明 //?但很明顯,此時結構體實例如果想調用?m3?方法 //?那么必須滿足?T?是?u8,W?是?f64 impl?Point ?{ ????fn?m3(&self)?{ ????????println!("我是?m3?方法") ????} } fn?main()?{ ????//?顯然?p1?可以同時調用?m1?和?m2?方法,但?m3?不行 ????//?因為?m3?要求?T?是一個?u8,而當前是?&str ????let?p1?=?Point{x:?"hello",?y:?3.14}; ????p1.m1();??//?我是?m1?方法 ????p1.m2();??//?我是?m2?方法 ????//?顯然?p2?可以同時調用?m1、m2、m3 ????//?另外這里的?x?可以直接寫成?123,無需在結尾加上?u8 ????//?因為?Rust?看到我們調用了?m3?方法,會自動推斷為?u8 ????let?p2?=?Point{x:?123u8,?y:?3.14}; ????p2.m1();??//?我是?m1?方法 ????p2.m2();??//?我是?m2?方法 ????p2.m3();??//?我是?m3?方法 ????//?顯然?p3?只能調用?m2?方法,因為?m2?對?T?和?W?沒有要求 ????//?但是像?m3?就不能調用,因為它是為? ?實現的方法 ????//?只有當?T、W?為?u8、f64?時才可以調用 ????//?顯然此時是不滿足的,因為都是?&str,至于?m1?方法也是同理 ????//?所以?p3?只能調用?m2,這個方法是為? ?實現的 ????//?而?K?和?S?也是泛型,可以代表任意類型,因此沒問題 ????let?p3?=?Point{x:?"3.14",?y:?"123"}; ????p3.m2();??//?我是?m2?方法 }
?
然后注意:我們上面的泛型本質上針對的還是結構體,而我們定義方法的時候也可以指定泛型,其語法和在函數中定義泛型是一樣的。
?
#[derive(Debug)] struct?Point?{ ????x:?T, ????y:?U, } //?使用?impl?時?Point?后面的泛型名稱可以任意 //?比如我們之前起名為?K?和?S,但這樣容易亂,因為字母太多了 //?所以建議:使用 impl 時的泛型和定義結構體時的泛型保持一致即可 impl ?Point ?{ ????//?方法類似于函數,它是一個獨立的個體,可以有自己獨立的泛型 ????//?然后返回值,因為?Point?里面是泛型,可以代表任意類型 ????//?那么自然也可以是其它的泛型 ????fn?m1 (self,?a:?V,?b:?W)?->?Point?{ ????????//?所以返回值的成員?x?的類型是?U,那么它應該來自于?self.y ????????//?成員?y?的類型是?W,它來自于參數?b ????????Point?{?x:?self.y,?y:?b?} ????} } fn?main()?{ ????//?T?是?i32,U?是?f64 ????let?p1?=?Point?{?x:?123,?y:?3.14?}; ????//?V?是?&str,W?是?(i32,?i32,?i32) ????println!("{:?}",?p1.m1("xx",?(1,?2,?3))) ????//?Point?{?x:?3.14,?y:?(1,?2,?3)?} }
?
以上就是 Rust 的泛型,當然在工作中我們不會聲明的這么復雜,這里只是為了更好掌握泛型的語法。
然后注意一下方法里面的 self,不是說方法的第一個參數應該是引用嗎?理論上是這樣的,但此處不行,而且如果寫成 &self 是會報錯的,會告訴我們沒有實現 Copy 這個 trait。
之所以會有這個現象,是因為我們在返回值當中將 self.y 賦值給了成員 x。那么問題來了,如果方法的第一個參數是引用,就意味著結構體在調用完方法之后還能繼續用,那么結構體內部所有成員的值都必須有效,否則結構體就沒法用了。這個動態數組相似,如果動態數組是有效的,那么內部的所有元素必須都是有效的,否則就可能訪問非法的內存。
因此在構建返回值、將 self.y 賦值給成員 x 的時候,就必須將 self.y 拷貝一份,并且還要滿足拷貝完之后數據是各自獨立的,互不影響。如果 self.y 的數據全部在棧上(可 Copy 的),那么這是沒問題的;如果涉及到堆,那么只能轉移 self.y 的所有權,因為 Rust 默認不會拷貝堆數據,但如果轉移所有權,那么方法調用完之后結構體就不能用了,這與我們將第一個參數聲明為引用的目的相矛盾。
所以 Rust 要求 self.y 必須是可 Copy 的,也就是數據必須都在棧上,這樣才能滿足在不拷貝堆數據的前提下,讓 self.y 賦值之后依舊保持有效。但問題是,self.y 的類型是 U,而 U 代表啥類型 Rust 又不知道,所以 Rust 認為 U 不是可 Copy 的,或者說沒有實現 Copy 這個 trait,于是報錯。
因此第一個參數必須聲明為 self,此時泛型是否實現 Copy 就不重要了,沒實現的話會直接轉移所有權。因為該結構體實例在調用完方法之后會被銷毀,不再被使用,那么此時可以轉移內部成員的所有權。正所謂人都沒了,還要這所有權有啥用,不如在銷毀之前將成員值的所有權交給別人。
最后說一下泛型代碼的性能,使用泛型的代碼和使用具體類型的速度是一樣的,因此這就要求 Rust 在編譯的時候能夠推斷出泛型的具體類型,所以類型要明確。
下一篇文章我們來學習 trait,這個 trait 我們提到過很多次了,但卻一直不知道它究竟代表啥,下一篇文章來揭開它的面紗。
評論
查看更多