[英]Generic struct over a generic type without type parameter
有可能在 Rust 中做這樣的事情嗎?
trait Foo<T> {}
struct A;
struct B;
struct Bar<T: Foo> {
a: T<A>,
b: T<B>
}
我知道我可以只為Bar
使用兩個參數,但我認為必須有更好的方法來做到這一點。
我想實現一個Graph
結構。 因為我不能只將節點和邊綁定到他們的父母生命周期,所以我想要像Rc
這樣的東西。 但是,有時可能需要一個可以從多個線程訪問的Graph
。 所以我必須同時實現Rc
和Arc
。
這就是Foo
的優點:我為Rc
和Arc
實現了Foo
( Foo
需要Deref
)並且我使用綁定到Foo
的參數T
。 這就是我想要為單線程和多線程使用一個結構的方式。
⇒ 這目前無法在 Rust 的類型系統中表達 ☹
幸運的是,由於本 RFC 中提出的“通用關聯類型”,未來將成為可能。 您可以在相應的跟蹤問題中跟蹤實施和穩定狀態。
這里最重要的詞是“HKT”(H igherķinded牛逼YPES)。 這是尚未在 Rust 中實現的類型系統的一個特性。 Haskell 提供 HKT。 在 C++ 世界中,HKT 被稱為“模板模板”。 上面提到的泛型關聯類型也是 HKT 的一種形式。
讓我們慢慢開始:我們所知道的簡單類型是什么? 讓我們列出一些類型: i32
、 bool
、 String
。 這些都是類型……您可以擁有這些類型的值(變量)。 Vec<i32>
呢? 這也是一種簡單的類型! 你可以有一個Vec<i32>
類型的變量,沒問題!
我們想將這些類型組合在一起; 我們稱這種分類為“某種類型”。 如果我們想以一種非常抽象的方式(關於類型的類型)進行討論,我們會選擇其他詞,在這種情況下是kind 。 甚至還有種類型的符號。 對於上面的簡單類型,我們說:這些類型的種類是
*
是的,只是一顆星星,很容易。 符號在以后更有意義!
讓我們搜索與簡單類型不同的類型。 Mutex<HashMap<Vec<i32>, String>>
? 不,它可能相當復雜,但它仍然是那種*
並且我們仍然可以擁有這種類型的變量。
Vec
呢? 是的,我們省略了尖括號。 是的,這確實是另一種類型! 我們可以有一個Vec
類型的變量嗎? 不! 什么的向量?!
這種捐贈方式為:
* -> *
這只是說:給我一個普通類型( *
),我將返回一個普通類型! 給這個東西( Vec
)一個普通類型i32
,它會返回一個普通類型Vec<i32>
! 它也稱為類型構造函數,因為它用於構造類型。 我們甚至可以更進一步:
* -> * -> *
這有點奇怪,因為它與柯里化有關,對於非 Haskell 程序員來說讀起來很奇怪。 但這意味着:給我兩種類型,我將返回一種類型。 讓我們考慮一個例子...... Result
! 在您提供兩個具體類型A
和B
之后Result<A, B>
Result
類型構造函數將返回一個具體類型Result<A, B>
。
術語更高級的類型只是指所有不是*
的類型,它們是類型構造函數。
當您編寫struct Bar<T: Foo>
您希望T
是* -> *
,這意味着:您可以給T
一個類型並接收一個簡單類型。 但正如我所說,這在 Rust 中還不能表達。 要使用類似的語法,人們可能會想象這在未來可以工作:
// This does NOT WORK!
struct Bar<for<U> T> where T<U>: Foo {
a: T<A>,
b: T<B>,
}
for<>
語法是從"higher-ranked trait bounds" (HRTB)借來的,它現在可用於對生命周期進行抽象(最常用於閉包)。
如果您想閱讀有關此主題的更多信息,請訪問以下鏈接:
獎勵:萬一將實現關聯類型構造函數,您的問題的解決方案(我認為,因為沒有辦法測試)!
我們必須在我們的實現中繞道而行,因為 RFC 不允許將Rc
作為類型參數直接傳遞。 可以這么說,它沒有直接介紹HKT。 但是正如 Niko 在他的博客文章中所說的那樣,我們可以通過使用所謂的“家族特征”,獲得與具有關聯類型構造函數的 HKT 相同的靈活性和功能。
/// This trait will be implemented for marker types, which serve as
/// kind of a proxy to get the real type.
trait RefCountedFamily {
/// An associated type constructor. `Ptr` is a type constructor, because
/// it is generic over another type (kind * -> *).
type Ptr<T>;
}
struct RcFamily;
impl RefCountedFamily for RcFamily {
/// In this implementation we say that the type constructor to construct
/// the pointer type is `Rc`.
type Ptr<T> = Rc<T>;
}
struct ArcFamily;
impl RefCountedFamily for ArcFamily {
type Ptr<T> = Arc<T>;
}
struct Graph<P: RefCountedFamily> {
// Here we use the type constructor to build our types
nodes: P::Ptr<Node>,
edges: P::Ptr<Edge>,
}
// Using the type is a bit awkward though:
type MultiThreadedGraph = Graph<ArcFamily>;
有關更多信息,您應該真正閱讀 Niko 的博客文章。 困難的話題解釋得足夠好,甚至我都能或多或少地理解它們!
編輯:我剛剛注意到 Niko 在他的博客文章中實際上使用了Arc
/ Rc
示例! 我完全忘記了這一點,我自己想到了上面的代碼……但也許我的潛意識還記得,因為我和 Niko 一樣選擇了幾個名字。 無論如何,這是他(可能更好)對這個問題的看法。
在某種程度上,Rust確實看起來很像 HKT(請參閱 Lukas 的回答以了解它們是什么),盡管有一些可以說是笨拙的語法。
首先,您需要為所需的指針類型定義接口,這可以使用泛型特征來完成。 例如:
trait SharedPointer<T>: Clone {
fn new(v: T) -> Self;
// more, eg: fn get(&self) -> &T;
}
加上一個泛型特性,它定義了一個關聯類型,它是你真正想要的類型,它必須實現你的接口:
trait Param<T> {
type Pointer: SharedPointer<T>;
}
接下來,我們為我們感興趣的類型實現該接口:
impl<T> SharedPointer<T> for Rc<T> {
fn new(v: T) -> Self {
Rc::new(v)
}
}
impl<T> SharedPointer<T> for Arc<T> {
fn new(v: T) -> Self {
Arc::new(v)
}
}
並定義一些實現上述Param
特征的虛擬類型。 這是關鍵部分; 我們可以有一個類型 ( RcParam
) 為任何T
實現Param<T>
,包括能夠提供一個類型,這意味着我們正在模擬一個更高級的類型。
struct RcParam;
struct ArcParam;
impl<T> Param<T> for RcParam {
type Pointer = Rc<T>;
}
impl<T> Param<T> for ArcParam {
type Pointer = Arc<T>;
}
最后我們可以使用它:
struct A;
struct B;
struct Foo<P: Param<A> + Param<B>> {
a: <P as Param<A>>::Pointer,
b: <P as Param<B>>::Pointer,
}
impl<P: Param<A> + Param<B>> Foo<P> {
fn new(a: A, b: B) -> Foo<P> {
Foo {
a: <P as Param<A>>::Pointer::new(a),
b: <P as Param<B>>::Pointer::new(b),
}
}
}
fn main() {
// Look ma, we're using a generic smart pointer type!
let foo = Foo::<RcParam>::new(A, B);
let afoo = Foo::<ArcParam>::new(A, B);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.