[英]Generic types that depend on another generic in Rust
我正在嘗試創建一個通用的結構,該結構具有通用實現特征的界限。 該特征本身是通用的。 這是在 Rust 1.49.0 中。
如果我這樣做:
trait Foo<T> {}
struct Baz<F: Foo<T>> {
x: F,
}
我得到一個編譯錯誤,因為T
沒有定義。 但如果我定義它:
trait Foo<T> {}
struct Baz<T, F: Foo<T>> {
x: F,
}
然后我得到一個編譯器錯誤,因為T
未使用。
唯一的選擇似乎是包含一個PhantomData<T>
字段,但如果我的通用依賴變得更加復雜,這開始變得更加笨拙:
use std::marker::PhantomData;
trait Foo<T> {}
struct Baz<T, U, F: Foo<T>, G: Foo<U>> {
phantom_t: PhantomData<T>,
phantom_u: PhantomData<U>,
x: F,
y: G,
}
我的一半領域是幻影。 該結構實際上是鬧鬼的。
我的問題是:最后編譯的示例真的是慣用的 Rust 嗎? 如果是這樣,為什么 Rust不能檢測到 Baz< T
Baz<T, Foo<T>>
中的 T 實際使用?
最后編譯的例子真的是慣用的Rust嗎?
存儲多個幻像類型參數的慣用方法是使用元組:
struct Baz<T, U, F: Foo<T>, G: Foo<U>> {
x: F,
y: G,
_t: PhantomData<(T, U)>,
}
為什么 Rust 檢測不到 Baz<
T
Baz<T, Foo<T>>
中的 T 實際被使用?
由於方差和drop check ,這實際上是預期的行為。 這里的想法是編譯器需要知道它可以對類型參數T
施加什么約束,並且PhantomData
類型的使用將指導編譯器如何做到這一點。
您可以了解更多關於PhantomData
以及它如何影響Rust nomicon中的方差的信息。
您可能應該做以下事情之一:
如果每個 struct F
可以通過多種方式實現Foo
( Foo<String>
, Foo<i32>
等,對於相同的F
),只需從 struct Baz
中刪除邊界。 T
的具體選擇在您決定使用它之前是不相關的,但是附加一個邊界會迫使您選擇一個T
結構將對其起作用,即使F
可以與多種類型一起使用。
相反,將<T>
參數和F: Foo<T>
綁定僅放在使用T
的impl
塊上(請參閱是否應該在 struct 和 impl 中重復 trait bounds? )。
如果每個 struct F
只希望以一種方式實現Foo
,則使T
成為Foo
的關聯類型而不是泛型類型。 請參閱何時適合使用關聯類型與泛型類型?
您可能不應該使用PhantomData<T>
。 這種標記類型不僅用於方差,正如 Ibraheem 的回答正確提到的那樣,而且還由drop 檢查器確定Baz<T>
邏輯上是否包含T
,並由編譯器確定應該為Baz<T>
實現哪些自動特征Baz<T>
。 如果您不小心如何使用它,您可能會以微妙的方式意外地過度約束或約束 API(請參閱讓結構體比賦予該結構體的方法的參數更長壽)。 更糟糕的是,由於類型的這些屬性在外部可見,因此PhantomData
的類型參數的選擇可能會暴露給外部代碼,這使得修復任何錯誤都成為重大更改。
如果您不知道PhantomData<T>
、 PhantomData<fn(T)>
、 PhantomData<fn() -> T>
、 PhantomData<fn(T) -> T>
、 PhantomData<*const T>
還是PhantomData<*mut T>
使用(六個有意義的不同的東西,這個列表並不詳盡),你應該通讀上面的鏈接並嘗試確定Baz
應該對T
有哪種方差、丟棄行為和自動特征行為. 這些是了解通用結構的重要品質,它們是其外部 API 的一部分。 如果其中任何一個對您的用例沒有意義,則可能意味着Baz
根本不應該是通用的T
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.