Как я могу массово реализовать Deserialize для всех типов, реализующих конкретный трейт?

Я десериализую файл конфигурации YAML с помощью Serde. Для большинства структур, в которые я выполняю десериализацию, все довольно просто - существует взаимно однозначное отношение между полями структур и свойствами в моем файле YAML.

В некоторых случаях все немного сложнее. Для них свойства в файле YAML лучше рассматривать как параметры конструктора. Фактическая структура будет иметь разные поля, рассчитанные на их основе.

Для этих случаев я написал отдельные структуры конфигурации, в которые я десериализуюсь. Для простоты рассмотрим этот глупый пример:

struct Message {
    text: String,
}

impl Message {
    fn from_config(config: MessageConfig) -> Message {
        Message {
            text: format!("{} {}", config.first_half, config.second_half),
        }
    }
}

#[derive(Deserialize)]
struct MessageConfig {
    first_half: String,
    second_half: String,
}

Чтобы Серде сделал для меня преобразование из MessageConfig в Message, я реализовал Deserialize для Message:

impl<'de> Deserialize<'de> for Message {
    fn deserialize<D>(deserializer: D) -> Result<Message, D::Error>
    where
        D: Deserializer<'de>,
    {
        MessageConfig::deserialize(deserializer).map(|config| Message::from_config(config))
    }
}

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

use serde::{Deserialize, Deserializer};
use serde_json;
#[macro_use]
extern crate serde_derive;

trait Configurable {
    type Config;
    fn from_config(config: Self::Config) -> Self;
}

impl<'de, T, C> Deserialize<'de> for T
where
    T: Configurable<Config = C>,
    C: Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
    {
        Self::Config::deserialize(deserializer).map(|config| Self::from_config(config))
    }
}

struct Message {
    text: String,
}

impl<'de> Configurable for Message {
    type Config = MessageConfig;

    fn from_config(config: MessageConfig) -> Message {
        Message {
            text: format!("{} {}", config.first_half, config.second_half),
        }
    }
}

#[derive(Deserialize)]
struct MessageConfig {
    first_half: String,
    second_half: String,
}

Однако компилятору это не нравится:

error[E0119]: conflicting implementations of trait `_IMPL_DESERIALIZE_FOR_MessageConfig::_serde::Deserialize<'_>` for type `std::boxed::Box<_>`:
  --> src/lib.rs:11:1
   |
11 | / impl<'de, T, C> Deserialize<'de> for T
12 | | where
13 | |     T: Configurable<Config = C>,
14 | |     C: Deserialize<'de>,
...  |
21 | |     }
22 | | }
   | |_^
   |
   = note: conflicting implementation in crate `serde`:
           - impl<'de, T> _IMPL_DESERIALIZE_FOR_MessageConfig::_serde::Deserialize<'de> for std::boxed::Box<T>
             where T: _IMPL_DESERIALIZE_FOR_MessageConfig::_serde::Deserialize<'de>;
   = note: downstream crates may implement trait `Configurable` for type `std::boxed::Box<_>`

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`)
  --> src/lib.rs:11:1
   |
11 | / impl<'de, T, C> Deserialize<'de> for T
12 | | where
13 | |     T: Configurable<Config = C>,
14 | |     C: Deserialize<'de>,
...  |
21 | |     }
22 | | }
   | |_^ type parameter `T` must be used as the type parameter for some local type
   |
   = note: only traits defined in the current crate can be implemented for a type parameter

Сообщения об ошибках не имеют для меня особого смысла. При чем тут Box? И можно ли как-нибудь заставить эту черту работать?


person Anders    schedule 02.01.2019    source источник
comment
@Shepmaster Не думаю, что это дубликат. Предлагаемый дубликат: структура имеет поле, которое ссылается на черту. Как мне десериализовать? Этот вопрос: я хочу массово реализовать Deserialize для всех типов, реализующих конкретную черту.   -  person Anders    schedule 03.01.2019
comment
Если все ваши структуры имеют одинаковую реализацию для десериализации, то почему бы не сделать их одной и той же реальной структурой (или все они содержат одну и ту же структуру) и не реализовать для этого Deserialize? Если они не одинаковы, тогда какова польза от добавления дополнительной косвенности?   -  person Shepmaster    schedule 03.01.2019
comment
Ошибка относительно проста. Serde реализует Deserialize для Box<T>. Ваша реализация охватывает T, который может быть Box<T>. Пользователь обоих ящиков может создать тип, который можно десериализовать либо как ваш код, либо как код в Serde. Это запрещено.   -  person Shepmaster    schedule 03.01.2019
comment
@Shepmaster Может быть, я делаю здесь основную ошибку. Я думал, что указав where T: Configurable<Config = C>, моя реализация Deserialize будет охватывать только разработчиков Configurable. Box<T> не является разработчиком Configurable, поэтому я решил, что проблем не возникнет.   -  person Anders    schedule 03.01.2019
comment
Что мешает кому-то реализовать Configurable для Box<MyOwnType>, когда MyOwnType также реализует Deserialize?   -  person Shepmaster    schedule 03.01.2019
comment
Ой. Не думал об этом. Спасибо. :-)   -  person Anders    schedule 03.01.2019


Ответы (1)


Я не уверен, есть ли способ определить такую ​​широкую черту, не вызывая противоречивых реализаций. Что вы можете сделать, так это использовать макрос, чтобы избежать повторения:

use serde::{Deserialize, Deserializer};
use serde_json;

use serde_json::Error;

#[macro_use]
extern crate serde_derive;

struct Message {
    text: String,
}

#[derive(Deserialize)]
struct MessageConfig {
    first_half: String,
    second_half: String,
}

impl Message {
    fn from_config(config: MessageConfig) -> Message {
        Message {
            text: format!("{} {}", config.first_half, config.second_half),
        }
    }
}

macro_rules! derive_configurable_serializer {
    ( $t:ident, $c:ident ) => {
        impl<'de> Deserialize<'de> for $t {
            fn deserialize<D>(deserializer: D) -> Result<$t, D::Error>
            where
                D: Deserializer<'de>,
            {
                $c::deserialize(deserializer).map(|config| $t::from_config(config))
            }
        }
    };
}

derive_configurable_serializer!(Message, MessageConfig);

fn main() -> Result<(), Error> {
    let data = r#"{ "first_half": "John", "second_half": "Doe" }"#;

    let p: Message = serde_json::from_str(data)?;

    println!("Hello, {}!", p.text);

    Ok(())
}
person Ramon Snir    schedule 02.01.2019
comment
Хорошая идея с макросом, и спасибо, что показали, как это сделать! Однако я не понимаю, что случилось с конфликтующими реализациями. Не существует типа, который реализует как конфигурируемый, так и десериализованный из какого-либо другого источника. - person Anders; 03.01.2019
comment
@ Андерс, честно говоря, я не знаю. Я пытался найти решение с этим признаком, получал различные сообщения об ошибках. Может быть, кто-нибудь с более глубоким пониманием особенностей Rust объяснит, как это сделать / почему это невозможно. - person Ramon Snir; 03.01.2019