I have a feeling that this is either trivial or impossible.
I want to create a type that shall be generic over some container type but not over their content. Imagine being able to choose between Arc or Rc for the fields but the public interface has no knowledge about the inner types.
My first attempt using generics:
struct BlueContainer<T>(T);
struct RedContainer<T>(T);
trait GetInner<T> { fn get(&self) -> T; }
impl<T> GetInner<T> for BlueContainer<T> {
fn get(&self) -> T { self.0 }
}
impl<T> GetInner<T> for RedContainer<T> {
fn get(&self) -> T { self.0 }
}
// Is there a way to express that `Variable` shall not be bound to a certain type
struct Carrier<C<Variable>: GetInner<Variable>> {
colorblind_string_container: C<String>,
colorblind_int_container: C<i32>,
red_string_container: RedContainer<String>,
blue_int_container: BlueContainer<i32>,
}
My second attempt using associated types
struct BlueContainer<T>(T);
struct RedContainer<T>(T);
trait GetInner {
type Inner;
fn get(&self) -> &Self::Inner;
}
impl<T> GetInner for BlueContainer<T> {
type Inner = T;
fn get(&self) -> &Self::Inner { &self.0 }
}
impl<T> GetInner for RedContainer<T> {
type Inner = T;
fn get(&self) -> &Self::Inner { &self.0}
}
struct Carrier<C> where C: GetInner {
colorblind_int_container: C, // should be something like `C::<i32>`
colorblind_string_container: C, // should be something like `C::<String>`
}
fn main() {
let carrier = Carrier {
colorblind_int_container: BlueContainer(123_i32),
// fails because BlueContainer has been used as i32 already
colorblind_string_container: BlueContainer("asdf".to_owned()),
};
println!("{}", carrier.colorblind_int_container.get());
println!("{}", carrier.colorblind_string_container.get());
}
In the long term, Generic Associated Types (GATs) will provide a simple solution to this problem.
For now, you may need to use slightly more complicated methods. For example, the archery crate provides traits and types for abstracting over Arc<T> and Rc<T>.
Technically you are requiring that RedContainer (or RedContainer<_> or for<T> RedContainer<T>) be a usable entity within a meta-programming approach on its own.
When using generics, this means that it should be a type. In Rust, this is not the case, but you can make your own type:
/// Container of a fixed `Elem` type (hence it being an assoc type instead of a generic type parameter)
trait Get {
type Elem;
fn get (self: &'_ Self) -> &'_ Self::Elem
;
}
// Example
struct RedContainer<Elem>(Elem);
impl<Elem> Get for RedContainer<Elem> {
type Elem = Elem;
fn get (self: &'_ Self) -> &'_ Elem
{
&self.0
}
}
/// Trait for a "higher-order type": expresses that `Self`
/// has a "`<Elem = _> hole"
/// The pseudo code "`Self<Elem>`" can be expressed with this
/// helper trait as `<Self as ContainerOf<Elem>>::T`
trait ContainerOf<Elem> {
/// The "return" type of this type-level function:
/// a concrete container type
type T : Get<Elem = Elem>;
}
// Example
enum RedContainer_ {}
impl<Elem> ContainerOf<Elem> for RedContainer_ {
type T = RedContainer<Elem>;
}
This way, you can write:
struct Carrier<C : ContainerOf<String> + ContainerOf<i32>> {
colorblind_string_container: <C as ContainerOf<String>>::T,
colorblind_int_container: <C as ContainerOf<i32>>::T,
red_string_container: RedContainer<String>,
blue_int_container: BlueContainer<i32>,
}
and use it as Carrier<RedContainer_> or Carrier<BlueContainer_>.
when using macros as the meta-programming approach, it turns out that the "identifier" RedContainer can directly be fed the <T> type parameter.
So, for instance, one possible macro-based API would be:
Thanks a lot for your help. I understood most of your solution but I'll have to play with that for a while to make sure I understand the implications for the rest of my project design.
@mbrubeck So the short answer is "It's trivial if it were possible" thanks for the links - reading myself into the matter right now.