Search code examples
genericsrusthigher-kinded-types

Converting a generic function argument to an `Arc` with a generic trait


Is it possible to have a function with a generic argument that converts that argument to an Arc with a generic trait? Something like this:

struct Foo<TypeOrTrait> {
    arc: Option<Arc<TypeOrTrait>>,
}

impl<TypeOrTrait> Foo<TypeOrTrait> {
    fn set<T>(&mut self, t: T) -> Arc<T> {
        let arc = Arc::new(t);
        self.arc = Some(arc.clone()); // Error
        arc
    }
}

The code complains that it can't convert Arc<T> to Arc<TypeOrTrait>.

In my code I don't have just the Arc that wraps TypeOrTrait, but a complex Arc<RwLock<Inner<TypeOrTrait>>> so I can't work around this by passing Arc<TypeOrTrait> to the set function.

The use case I have is that I want to have a collection that only implements a trait, but also want to have a way to access/modify the values using their original types. Something like this:

let foo: Foo<fmt::Display> = Foo { arc: None };

let value = foo.set(1u8);
assert_eq!(*value, 1u8);
assert_eq!(foo.bar.unwrap().to_string(), "1");

let value = foo.set(2u16);
assert_eq!(*value, 2u16);
assert_eq!(foo.bar.unwrap().to_string(), "2");

I was told that this would require higher kinded types, is that correct? If so, could it be emulated with something like what they do here (my Rust fu isn't on a par with that)?


Solution

  • If you have only few traits and types, you can use a custom trait:

    pub trait ConvertArc<To: ?Sized> {
        fn convert(self: Arc<Self>) -> Arc<To>;
    }
    
    impl<T: ?Sized> ConvertArc<T> for T {
        fn convert(self: Arc<Self>) -> Arc<Self> { self }
    }
    impl ConvertArc<dyn Trait> for Struct {
        fn convert(self: Arc<Self>) -> Arc<dyn Trait> { self }
    }
    
    struct S<TypeOrTrait: ?Sized> {
        arc: Option<Arc<TypeOrTrait>>,
    }
    
    impl<TypeOrTrait: ?Sized> S<TypeOrTrait> {
        fn set<T: ConvertArc<TypeOrTrait>>(&mut self, t: T) -> Arc<T> {
            let arc = Arc::new(t);
            self.arc = Some(arc.clone().convert());
            arc
        }
    }
    

    If you have many traits or types... well... I think you don't have a solution. You can almost use std::marker::Unsize on nightly, but not, because you want to be able to use the same type for both too.