Как я могу написать метод для переноса любого значения в моем типе, которое можно вызывать несколько раз, не требуя аннотаций типов? - PullRequest
1 голос
/ 01 апреля 2019

Я написал тип Wrapper<T>, который содержит значение T:

struct Wrapper<T>(T);

Мне нужен метод to_wrap, который позволил бы мне написать такой код, где b равно Wrapper<i32>, а c равно Wrapper<i32>:

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

Я хочу, чтобы v.to_wrap() всегда производил Wrapper<T>, где T равно НЕ a Wrapper. Если v является Wrapper<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);
}

Игровая площадка

При компиляции аннотации типа из 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 ]

2 голосов
/ 02 апреля 2019

Вы можете достичь чего-то подобного, не используя специализацию, но используя две разные черты и автоматическую черту, чтобы различать их.

#![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);
}

Как и в случае предложения Чабапока , вы не можете использовать эту технику для написания универсальной функции, которая ведет себя так, когда задан один тип, и другим способом - с другим типом (хотя см. Ссылки ниже). Но вы можете использовать его, когда конкретный тип известен компилятору, но не программисту - в качестве возможного варианта использования приходят на ум макросы.

Он имеет еще одно преимущество в том, что нет никакой двусмысленности, из-за которой метод to_wrap будет вызываться для любого типа. Каждый тип имеет не более одного to_wrap метода, так как Wrapper s имеет только ToWrapperSelf::is_wrap, а не Wrapper s имеет ToWrapper::is_wrap.

Еще одно преимущество dis заключается в том, что !IsWrap для Wrapper<T> является "заразным": любой тип, который содержит или может содержать Wrapper<T>, будет также автоматически !IsWrap. Если вы вызовете .to_wrap() для такого типа, компилятор не сможет найти метод и выдаст ошибку. Если это проблема, вы можете вручную реализовать IsWrap для этих типов, но может быть более разумно искать другое, менее хрупкое решение.

(Единственное исключение из вышеперечисленного - Box<Wrapper<T>>: вы можете позвонить ToWrapperSelf::to_wrap, чтобы получить Wrapper<T>. Это происходит из-за правил автоопределения и потому, что Box является специальным .)

Смотри также

2 голосов
/ 01 апреля 2019

Я не верю, что вы можете достичь своей цели реализации единой черты, используя специализацию на данный момент.

Ваше определение черты допускает несколько реализаций черты для одного и того же типа:

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 для неизвестного типа, который произведет другой неизвестный тип!

Обычно использование связанных типов было бы решением, но вы не можете переключиться на них здесь из-за того, как они взаимодействуют со специализацией.

Смотри также:

0 голосов
/ 01 апреля 2019
#[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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...