1

I wrote a type Wrapper<T> which contains a value of T:

struct Wrapper<T>(T);

I want a method to_wrap which would allow me to write code like this, where b is Wrapper<i32> and c is Wrapper<i32>:

let a = 12i32;
let b = a.to_wrap();
let c = b.to_wrap();

I want v.to_wrap() to always produce a Wrapper<T> where T is NOT a Wrapper. If v is a Wrapper<T>, v.to_wrap() will also be a Wrapper<T> with the same value.

The code I wrote closest to my idea is:

#![feature(specialization)]

#[derive(Debug)]
struct Wrapper<T>(T);

trait ToWrapper<W> {
    fn to_wrap(self) -> W;
}

impl<T> ToWrapper<Wrapper<T>> for T {
    default fn to_wrap(self) -> Wrapper<T> {
        Wrapper(self)
    }
}

impl<T> ToWrapper<Wrapper<T>> for Wrapper<T> {
    fn to_wrap(self) -> Self {
        self
    }
}

fn main() {
    let a = 1i32;
    println!("{:?}", a);
    let a = 1.to_wrap();
    println!("{:?}", a);
    let a: Wrapper<i32> = 1.to_wrap().to_wrap();
    // let a = 1.to_wrap().to_wrap();
    // boom with `cannot infer type`
    println!("{:?}", a);
}

Playground

This has a compilation error if I erase the type annotation on from let a: Wrapper<i32> = 1.to_wrap().to_wrap():

error[E0282]: type annotations needed
  --> src/main.rs:27:9
   |
27 |     let a = 1.to_wrap().to_wrap();
   |         ^
   |         |
   |         cannot infer type
   |         consider giving `a` a type

I want the Rust compiler to automatically derive the type of 1.to_wrap().to_wrap(). How can I write the correct version, or why is it not possible to write it?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
chirsz
  • 11
  • 3

3 Answers3

2

I do not believe you can accomplish your goal of implementing a single trait using specialization at this point in time.

Your trait definition allows for multiple implementations of the trait for the same type:

trait ToWrapper<W> {
    fn to_wrap(self) -> W;
}

impl ToWrapper<i32> for u8 {
    fn to_wrap(self) -> i32 {
        i32::from(self)
    }
}

impl ToWrapper<i16> for u8 {
    fn to_wrap(self) -> i16 {
        i16::from(self)
    }
}

With such a setup, it's impossible to know what the resulting type of to_wrap should be; you will always need to provide the output type somehow. Then you compound the problems by attempting to call to_wrap on an unknown type that will produce another unknown type!

Normally, using associated types would be the solution, but you cannot switch to those here because of how they interact with specialization.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks. I have tried using associated type and as you say it can't complie although I annotate the type. – chirsz Apr 03 '19 at 11:04
2

You can achieve something like this on nightly, not using specialization, but using two different traits and an auto trait to distinguish between them.

#![feature(auto_traits)]
#![feature(negative_impls)]

auto trait IsWrap {}

#[derive(Debug)]
struct Wrapper<T>(T);

impl<T> !IsWrap for Wrapper<T> {}

trait ToWrapper: Sized {
    fn to_wrap(self) -> Wrapper<Self>;
}

impl<T: IsWrap> ToWrapper for T {
    fn to_wrap(self) -> Wrapper<T> {
        Wrapper(self)
    }
}

trait ToWrapperSelf {
    fn to_wrap(self) -> Self;
}

impl<T> ToWrapperSelf for Wrapper<T> {
    fn to_wrap(self) -> Self {
        self
    }
}

fn main() {
    let a = 1.to_wrap();
    println!("{:?}", a);
    let a = 1.to_wrap().to_wrap(); 
    println!("{:?}", a);
}

As with chabapok's suggestion, you can't use this technique to write a generic function that behaves one way when given one type and another way with another type (although see the links below). But you can use it when the concrete type is known to the compiler but not to the programmer -- macros come to mind as a possible use case.

It has one further advantage in that there is no possible ambiguity over which to_wrap method will be called on any type. Every type has at most one to_wrap method, since Wrappers only have ToWrapperSelf::is_wrap and non-Wrappers only have ToWrapper::is_wrap.

One other disadvantage is that !IsWrap for Wrapper<T> is "infectious": any type that contains or might contain a Wrapper<T> will also be automatically !IsWrap. If you call .to_wrap() on such a type, the compiler will not be able to find the method and will issue an error. If this is a problem, you can manually implement IsWrap for these types, but it might be more prudent to look for another, less fragile solution.

(The only exception to the above is Box<Wrapper<T>>: you can call ToWrapperSelf::to_wrap on it to get a Wrapper<T>. This happens due to the autodereferencing rules and because Box is special.)

See also

trent
  • 25,033
  • 7
  • 51
  • 90
0
#[derive(Debug)]
struct Wrapper<T>(T);

trait ToWrapper<W> {
    fn to_wrap(self) -> Wrapper<W>;
}

impl<T> ToWrapper<T> for T {
    fn to_wrap(self) -> Wrapper<T> {
        Wrapper(self)
    }
}

impl<T> Wrapper<T> {
    fn to_wrap(self) -> Wrapper<T> {
        self
    }
}

fn main() {
    let a = 1i32;
    println!("{:?}", a);
    let a = 1.to_wrap();
    println!("{:?}", a);
    let a: Wrapper<i32> = 1.to_wrap().to_wrap();
    let b = 1.to_wrap().to_wrap();
    println!("{:?}", a);
    println!("{:?}", b);
}
chabapok
  • 921
  • 1
  • 8
  • 14
  • 1
    Please use some words to explain how your solution works, as well as identifying any weaknesses it might have. – Shepmaster Apr 01 '19 at 17:59
  • Thank. I found there would be unexpected behavior when defining a generic function like `fn foo(value:ToWrapper);`; When I give a `value:Wrapper` to it, it would use ` as ToWrapper>>::to_wrap` rather than `Wrapper::to_wrap`. – chirsz Apr 03 '19 at 10:52