General structure over a generic type without a type parameter

Is it possible to do something like this in Rust?

trait Foo<T> {} struct A; struct B; struct Bar<T: Foo> { a: T<A>, b: T<B> } 

I know that I could just use two parameters for Bar , but I think there should be a better way to do this.

I want to implement a Graph structure. Since I cannot just snap nodes and edges to their lives, I want to have something like Rc . However, sometimes you may need Graph with access from multiple threads. So I had to have an implementation with Rc and Arc .

That Foo is useful: I implement Foo for Rc and Arc ( Foo will require Deref ), and I use the T parameter bound to Foo . This is how I wanted to have one structure for using a thread and multiple threads.

+7
generics rust
source share
2 answers

⇒ Currently not possible for expression in a system like Rust

An important term here is "HKT" ( h is stronger k inded t ypes). This is a feature of the type system that is not yet implemented in Rust. Haskell offers HKT. In the C ++ world, HKTs are known as "template templates."

But what is HKT, really?

Let's start slowly: what is a simple type, as we know it? We list some types: i32 , bool , String . These are all types ... you may have a value (variable) of these types. What about Vec<i32> ? This is also a simple type! You can have a variable like Vec<i32> , no problem!

We want to group these types together; we call this categorization "type view". If we want to speak very abstractly (about types of types), we choose other words kind in this case. There is even a notation for type types. For our simple types above we say: types of these types

 * 

Yes, just a star, very easy. Designations make sense later!


Allows you to search for types that are different from our simple types. Mutex<HashMap<Vec<i32>, String>> ? No, this is rather complicated, perhaps, but it still has the form * , and we can still have a variable of this type.

How about Vec ? Yes, we omitted the angle brackets. Yes, this is really a different type! Can we have a variable of type Vec ? Not! The vector of what ?!

This view is referred to as:

 * -> * 

It just says: give me the regular type ( * ) and I will return the regular type! Give the normal type i32 this thing ( Vec ) and it will return the normal type Vec<i32> ! It is also called a type constructor because it is used to build types. We can even go further:

 * -> * -> * 

This is a bit strange because it is related to currying and reads odd for a non-Haskell programmer. But that means: give me two types, and I will return the type. Think of an example ... Result ! The constructor of the Result type returns the concrete type Result<A, B> after you have provided two specific types A and B

The term higher type types applies only to all types of types that are not * , which are type constructors.

In your example

When you write struct Bar<T: Foo> , you want T have the form * -> * , which means: you can specify one type of T and get a simple type. But, as I said, this is not yet expressed in Rust. To use a similar syntax, we can assume that this may work in the future:

 // This does NOT WORK! struct Bar<for<U> T> where T<U>: Foo { a: T<A>, b: T<B>, } 

The syntax for<> borrowed from the "higher ranking" (HRTB) , which can be used today to abstract throughout life (most often used with closure).

References

If you want to know more about this topic, here are a few links:


Bonus : a solution to your problem if related type constructors are implemented (I think, since there is no way to test)!

We should take a detour in our implementation, since the RFC will not allow passing Rc as a type parameter directly. He does not introduce HKT directly, so to speak. But, as Nico claims in his blog, we can have the same flexibility and power as HKT with related type constructors, using the so-called "family traits."

 /// 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>; 

For more information, you really should read Niko's blog entries. Difficult topics are explained well enough that even I can understand them more or less!

EDIT : I just noticed that Niko actually used the Arc / Rc example on his blog! I completely forgot about it and thought about the code above myself ... but maybe my subconscious mind was still remembered, because I choose several names in the same way as Nico did. In any case, here it is (perhaps better) the problem .

+15
source share

In some ways, Rust has something very similar to HKT (see Lucas for a good description of what they are), although with some uncomfortable syntax.

First, you need to define an interface for the type of pointer you need, which can be performed using a common attribute. For example:

 trait SharedPointer<T>: Clone { fn new(v: T) -> Self; // more, eg: fn get(&self) -> &T; } 

Plus a common attribute that defines the associated type that you really need, which your interface should implement:

 trait Param<T> { type Pointer: SharedPointer<T>; } 

Then we implement this interface for the types that interest us:

 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) } } 

And define some dummy types that implement the above Param trait. This is the key part; we can have one type ( RcParam ) that implements Param<T> for any T , including the ability to supply a type, which means that we are modeling a higher type.

 struct RcParam; struct ArcParam; impl<T> Param<T> for RcParam { type Pointer = Rc<T>; } impl<T> Param<T> for ArcParam { type Pointer = Arc<T>; } 

And finally, we can use it:

 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); } 

Playground

+6
source share

All Articles