我该如何编写一种方法来将类型中的任何值包装起来,而无需多次使用类型注释,该值可以多次调用?

时间:2019-04-01 13:10:58

标签: rust

我写了一个类型Wrapper<T>,其中包含值T

struct Wrapper<T>(T);

我想要一个方法to_wrap,它允许我这样编写代码,其中bWrapper<i32>,而cWrapper<i32>

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

我希望v.to_wrap()总是产生Wrapper<T>,其中T NOT 不是Wrapper。如果vWrapper<T>,则v.to_wrap()也将是具有相同值的Wrapper<T>

我写的最接近我的想法的代码是:

#![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

如果我从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

我希望Rust编译器自动派生1.to_wrap().to_wrap()的类型。如何编写正确的版本,或者为什么无法编写?

3 个答案:

答案 0 :(得分:2)

我认为您目前无法通过专业化来实现实现单个特征的目标。

您的特征定义允许同一类型的特征的多种实现:

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

通过这样的设置,不可能知道to_wrap 应该的结果类型;您将始终需要以某种方式提供输出类型。然后,通过尝试调用会产生另一个未知类型的未知类型to_wrap,使问题更加复杂!

通常,使用关联的类型将是解决方案,但是由于它们与专业化的交互方式,您不能在此切换到那些类型。

另请参阅:

答案 1 :(得分:2)

您可以实现这样的目标,而不是使用专业化,而是使用两个不同的特征和一个自动特征来区分它们。

#![feature(optin_builtin_traits)]

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

chabapok's suggestion一样,您不能使用此技术来编写一个通用函数,该通用函数在给定一种类型时会以一种方式运行,而在另一种类型时会以另一种方式运行(尽管请参见下面的链接)。但是,当编译器知道具体类型而不是程序员知道具体类型时,您可以使用它-宏是一种可能的用例。

它的另一个优点是,在任何类型上调用to_wrap方法都没有歧义。每个类型最多具有一种to_wrap方法,因为Wrapper仅具有ToWrapperSelf::is_wrap,非Wrapper仅具有ToWrapper::is_wrap

另一个 dis 的优点是!IsWrap的{​​{1}}是“传染性的”:任何包含或可能包含Wrapper<T>的类型也会 自动为Wrapper<T>。如果您在这种类型上调用!IsWrap,则编译器将无法找到该方法,并会发出错误消息。如果这是一个问题,则可以为这些类型手动实现.to_wrap(),但是寻找另一个较不脆弱的解决方案可能更为谨慎。

(以上唯一的例外是IsWrap:您可以在其上调用Box<Wrapper<T>>以获得ToWrapperSelf::to_wrap。由于自动取消引用规则和Box is special

另请参见

答案 2 :(得分: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);
}