簡體   English   中英

特質的泛型類型和通用的相關類型之間有什么區別?

[英]What's the difference between a trait's generic type and a generic associated type?

在Rust中提供通用關聯類型之前會詢問此問題,盡管它們是提出開發的

我的理解是,特質泛型和相關類型在它們可以綁定到結構的類型數量上有所不同。

泛型可以綁定任意數量的類型:

struct Struct;

trait Generic<G> {
    fn generic(&self, generic: G);
}

impl<G> Generic<G> for Struct {
    fn generic(&self, _: G) {}
}

fn main() {
    Struct.generic(1);
    Struct.generic("a");
}

關聯類型只綁定1種類型:

struct Struct;

trait Associated {
    type Associated;

    fn associated(&self, associated: Self::Associated);
}

impl Associated for Struct {
    type Associated = u32;

    fn associated(&self, _: Self::Associated) {}
}

fn main() {
    Struct.associated(1);
    // Struct.associated("a"); // `expected u32, found reference`
}

通用關聯類型是這兩者的混合。 它們綁定到一個類型正好相關的1個生成器,而這個生成器又可以關聯任意數量的類型。 那么前面例子中的Generic與這個通用關聯類型有什么區別?

struct Struct;

trait GenericAssociated {
    type GenericAssociated;

    fn associated(&self, associated: Self::GenericAssociated);
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;

    fn associated(&self, _: Self::GenericAssociated) {}
}

有什么不同?

通用關聯類型(GAT)是相關類型 ,它們本身是通用的 RFC以一個激勵性的例子開始,強調我的:

考慮以下特征作為代表性激勵示例:

 trait StreamingIterator { type Item<'a>; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; } 

這個特性非常有用 - 它允許一種迭代器產生的值,這些值的壽命與傳遞給next的引用的生命周期相關。 該特征的一個特別明顯的用例是在向量上的迭代器,其在每次迭代時產生重疊的,可變的子序列。 使用標准的Iterator接口,這樣的實現將是無效的,因為每個切片都需要與迭代器一樣長,而不是只要next啟動的借用。

這種特性不能用Rust表達 ,因為它依賴於一種更高級的多態性。 此RFC將擴展Rust以包括特定形式的高級多態性,這里將其稱為關聯類型構造函數。 此功能有許多應用程序,但主要應用程序與StreamingIterator特征的行相同:定義特征,這些特征產生的類型的生命周期與接收器類型的本地借用相關聯。

注意關聯類型Item如何具有通用生命周期'a RFC中的大多數示例都使用生命周期,但也有使用泛型類型的示例

 trait PointerFamily { type Pointer<T>: Deref<Target = T>; fn new<T>(value: T) -> Self::Pointer<T>; } 

注意關聯類型Pointer如何具有泛型類型T

你的具體例子

上一個示例中的Generic與此通用關聯類型之間的區別是什么

可能沒有,GAT的存在對你的情況沒有幫助,這似乎不需要一個本身就是通用的相關類型。

讓我們再看看你的最后一個例子(由我縮短):

trait GenericAssociated {
    type GenericAssociated;
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;
}

配備通用關聯類型! 您只是在impl塊上使用泛型類型,並將其分配給關聯類型。 嗯,好的,我可以看到混亂的來源。

“類型參數G不受impl trait,self type或謂詞約束”的示例錯誤。 實施GAT時,這不會改變,因為這與GAT無關。

在您的示例中使用GAT可能如下所示:

trait Associated {
    type Associated<T>; // <-- note the `<T>`! The type itself is 
                        //     generic over another type!

    // Here we can use our GAT with different concrete types 
    fn user_choosen<X>(&self, v: X) -> Self::Associated<X>;
    fn fixed(&self, b: bool) -> Self::Associated<bool>;
}

impl Associated for Struct {
    // When assigning a type, we can use that generic parameter `T`. So in fact,
    // we are only assigning a type constructor.
    type Associated<T> = Option<T>;

    fn user_choosen<X>(&self, v: X) -> Self::Associated<X> {
        Some(x)
    }
    fn fixed(&self, b: bool) -> Self::Associated<bool> {
        Some(b)
    }
}

fn main() {
    Struct.user_choosen(1);    // results in `Option<i32>`
    Struct.user_choosen("a");  // results in `Option<&str>`
    Struct.fixed(true);        // results in `Option<bool>`
    Struct.fixed(1);           // error
}

但是回答你的主要問題:

特質的泛型類型和通用的相關類型之間有什么區別?

簡而言之: 它們允許延遲具體類型(或壽命)的應用,這使得整個類型系統更加強大。

RFC中有許多動機示例,最值得注意的是流迭代器和指針族示例。 讓我們快速了解為什么無法使用特征上的泛型實現流式迭代器。

流式迭代器的GAT版本如下所示:

trait Iterator {
    type Item<'a>;
    fn next(&self) -> Option<Self::Item<'_>>;
}

在當前的Rust中,我們可以將life參數放在trait而不是關聯的類型上:

trait Iterator<'a> {
    type Item;
    fn next(&'a self) -> Option<Self::Item>;
}

到目前為止一切順利:所有迭代器都可以像以前一樣實現這個特性。 但是如果我們想要使用呢?

fn count<I: Iterator<'???>>(it: I) -> usize {
    let mut count = 0;
    while let Some(_) = it.next() {
        count += 1;
    }
    count
}

我們應該注釋什么生命周期? 除了注釋'static生命周期”,我們有兩個選擇:

  • fn count<'a, I: Iterator<'a>>(it: I) :這不起作用,因為調用者選擇函數的泛型類型。 it (它將next調用中成為self )存在於我們的堆棧框架中。 這意味着調用者不知道it的生命周期。 因此我們得到了一個編譯器( Playground )。 這不是一個選擇。
  • fn count<I: for<'a> Iterator<'a>>(it: I) (使用HRTB):這似乎有效,但它有微妙的問題。 現在我們要求I任何生命周期中實現Iterator 'a 這對於許多迭代器來說不是問題,但是一些迭代器會返回永遠不會生命的項目,因此它們無法在任何生命周期內實現Iterator - 只比生命周期短。 使用這些排名較高的特征界限往往導致秘密的'static邊界”,這是非常有限的。 所以這也並不總是有效。

正如你所看到的:我們無法正確地記下I的界限。 實際上,我們甚至不想提及count函數簽名中的生命周期! 它沒有必要。 這正是GAT允許我們做的事情(除其他事項外)。 通過GAT,我們可以寫:

fn count<I: Iterator>(it: I) { ... }

它會起作用。 因為“具體生命的應用”只在我們next打電話時才會發生。

如果您對更多信息感興趣,可以查看我的博客文章“解決沒有GAT的廣義流式迭代器問題” ,我嘗試在特性上使用泛型類型來解決缺少GAT問題。 而且(劇透):它通常不起作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM