Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement something similar to C++'s `std::remove_reference` in Rust?

Tags:

rust

I'd like to have a tool to remove the reference from a type if the type is a reference. Something like this (pseudo code):

remove_ref(i32)      == i32
remove_ref(&i32)     == i32
remove_ref(&&i32)    == i32
remove_ref(&mut i32) == i32

C++ has std::remove_reference in the standard library which can do exactly what I want. I tried to implement the same in Rust, but I can't get it working. Right now, the only way to "output" types in Rust are associated types on traits (I think). I tried something like this (Playground):

#![feature(specialization)]

trait RemoveRef {
    type WithoutRef;
}

default impl<T> RemoveRef for T {
    type WithoutRef = T;
}

impl<'a, T: RemoveRef> RemoveRef for &'a T {
    type WithoutRef = T::WithoutRef;
}

And in fact this compiles. Promising! (yes, this doesn't account for mutable references yet). However, everything explodes when I try to use it:

let _: <i32 as RemoveRef>::WithoutRef = 3;
let _: <&i32 as RemoveRef>::WithoutRef = 3;
let _: <&&i32 as RemoveRef>::WithoutRef = 3;

The first line leads to "overflow evaluating the requirement i32: RemoveRef". The other two lines produce the error "the trait bound &i32: RemoveRef is not satisfied". I'm not sure if I just don't understand this or if specialization is broken. (Related: I got another strange error with very similar code here)

I was thinking about other possibilities to implement this: maybe put a type parameter on the trait? Maybe GATs could help here? Are there other features in the language that allow mapping from one type to another type?

Is there any way to implement something like this in Rust?

like image 864
Lukas Kalbertodt Avatar asked Jun 25 '18 15:06

Lukas Kalbertodt


1 Answers

Here is a simple approach that works without the specialization feature:

use std::marker::PhantomData;

trait RemoveRef {
    type WithoutRef;
}

struct Ref<T> {
    phantom: PhantomData<T>,
}

impl<T> RemoveRef for Ref<T> {
    type WithoutRef = T;
}

impl<'a, T: RemoveRef> RemoveRef for &'a T {
    type WithoutRef = T::WithoutRef;
}

fn main() {
    let _: <Ref<i32> as RemoveRef>::WithoutRef = 3;
    let _: <&Ref<i32> as RemoveRef>::WithoutRef = 3;
    let _: <&&&&Ref<i32> as RemoveRef>::WithoutRef = 3;
}

Not sure though if it can be made compatible with your actual use case in this form or if it is useful at all.

Alternatively, it is of course also possible to replace your generic exit condition (impl<T> RemoveRef for T) with implementations for concrete types:

impl RemoveRef for i32 {
    type WithoutRef = Self;
}

This enables your original test code:

let _: <i32 as RemoveRef>::WithoutRef = 3;
let _: <&i32 as RemoveRef>::WithoutRef = 3;
let _: <&&i32 as RemoveRef>::WithoutRef = 3;

AFAIK specializations cannot help you to fix overlapping issue like the one between for T and for &'a T at this point. It would require features like negative trait bounds.

All items in a default impl are implicitly default. If you move the default keyword to the associated type in your code you get rid of the evaluation overflow, but you get other errors:

impl<T> RemoveRef for T {
    default type WithoutRef = T;
}
error[E0308]: mismatched types
  --> src/main.rs:16:45
   |
16 |     let _: <i32 as RemoveRef>::WithoutRef = 3;
   |                                             ^ expected associated type, found integral variable
   |
   = note: expected type `<i32 as RemoveRef>::WithoutRef`
              found type `{integer}`

This fails for the same reason as discussed here: Mismatch between associated type and type parameter only when impl is marked default Assigning T to WithoutRef in combination with default does not constrain WithoutRef to type T.

like image 65
Calculator Avatar answered Sep 21 '22 05:09

Calculator