Comment renvoyer un type associé à partir d'un trait lié à un trait de rang supérieur?

11

J'ai un trait qui a une fonction pour désérialiser un type associé. Cependant, ce type associé doit avoir une durée de vie que l'appelant décide, j'ai donc un trait distinct pour lequel j'utilise un trait de rang supérieur pour qu'il puisse être désérialisé pour toute durée de vie.

J'ai besoin d'utiliser une fermeture qui renvoie ce type associé.

J'ai le code suivant pour le faire:

#![allow(unreachable_code)]

use std::marker::PhantomData;

trait Endpoint: for<'a> EndpointBody<'a> {}
trait EndpointBody<'a> {
    type Out: 'a;
    fn serialize(body: &Self::Out) -> Vec<u8>;
    fn deserialize(raw_body: &'a [u8]) -> Self::Out;
}

// /////////////////////////////////////////////////////////

/// Trait object compatible handler
trait Handler {
    fn execute(&self, raw_body: &[u8]) -> Vec<u8>;
}

/// Wraps a function for an endpoint, convertint it to a Handler
struct FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
{
    func: F,
    _ph: PhantomData<EP>,
}
impl<EP, F> FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
{
    pub fn new(func: F) -> Self {
        Self {
            func,
            _ph: PhantomData,
        }
    }
}
impl<EP, F> Handler for FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
{
    fn execute(&self, in_raw_body: &[u8]) -> Vec<u8> {
        let body = (self.func)(in_raw_body);
        let serialized_body = unimplemented!();
        return serialized_body;
    }
}

// /////////////////////////////////////////////////////////

/// Collection of handlers
struct Handlers(Vec<Box<dyn Handler>>);
impl Handlers {
    pub fn new() -> Self {
        Self(vec![])
    }

    pub fn handle<EP: 'static, F>(&mut self, func: F)
    where
        EP: Endpoint,
        F: 'static + for<'a> Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
    {
        self.0.push(Box::new(FnHandler::<EP, F>::new(func)));
    }
}

// /////////////////////////////////////////////////////////

struct MyEndpoint;
struct MyEndpointBody<'a> {
    pub string: &'a str,
}
impl Endpoint for MyEndpoint {}
impl<'a> EndpointBody<'a> for MyEndpoint {
    type Out = MyEndpointBody<'a>;

    fn serialize(body: &Self::Out) -> Vec<u8> {
        unimplemented!()
    }
    fn deserialize(raw_body: &'a [u8]) -> Self::Out {
        unimplemented!()
    }
}

// /////////////////////////////////////////////////////////

fn main() {
    let mut handlers = Handlers::new();
    handlers.handle::<MyEndpoint, _>(|_body| MyEndpointBody {
        string: "test string",
    });

    handlers.0[1].execute(&[]);
}

Je pense que cela devrait fonctionner, mais quand je le vérifie, j'obtiens une erreur de type:

error[E0271]: type mismatch resolving `for<'a> <[closure@src/main.rs:92:38: 94:6] as std::ops::FnOnce<(&'a [u8],)>>::Output == <MyEndpoint as EndpointBody<'a>>::Out`
  --> src/main.rs:92:14
   |
92 |     handlers.handle::<MyEndpoint, _>(|_body| MyEndpointBody {
   |              ^^^^^^ expected struct `MyEndpointBody`, found associated type
   |
   = note:       expected struct `MyEndpointBody<'_>`
           found associated type `<MyEndpoint as EndpointBody<'_>>::Out`
   = note: consider constraining the associated type `<MyEndpoint as EndpointBody<'_>>::Out` to `MyEndpointBody<'_>`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

C'est déroutant parce que MyEndpoint::Outc'est un MyEndpointBody, que je reviens de la fermeture, mais Rust ne pense pas qu'ils sont du même type. Je suppose que c'est parce que Rust choisit des durées de vie anonymes incompatibles pour le MyEndpointBodytype, mais je ne sais pas comment résoudre ce problème.

Comment puis-je obtenir ce code pour que je puisse utiliser une fermeture avec un type associé HRTB?

Colonel trente-deux
la source

Réponses:

4

Le bouclage de fermeture du type de retour dans un nouveau type résout le problème:

#![allow(unreachable_code)]

use std::marker::PhantomData;

trait Endpoint: for<'a> EndpointBody<'a> {}
trait EndpointBody<'a> {
    type Out: 'a;
    fn serialize(body: &Self::Out) -> Vec<u8>;
    fn deserialize(raw_body: &'a [u8]) -> Self::Out;
}

struct EPOut<'a, EP: Endpoint>(<EP as EndpointBody<'a>>::Out);

// /////////////////////////////////////////////////////////

/// Trait object compatible handler
trait Handler {
    fn execute(&self, raw_body: &[u8]) -> Vec<u8>;
}

/// Wraps a function for an endpoint, convertint it to a Handler
struct FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> EPOut<'a, EP>,
{
    func: F,
    _ph: PhantomData<EP>,
}
impl<EP, F> FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> EPOut<'a, EP>,
{
    pub fn new(func: F) -> Self {
        Self {
            func,
            _ph: PhantomData,
        }
    }
}
impl<EP, F> Handler for FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> EPOut<'a, EP>,
{
    fn execute(&self, in_raw_body: &[u8]) -> Vec<u8> {
        let body = (self.func)(in_raw_body);
        let serialized_body = unimplemented!();
        return serialized_body;
    }
}

// /////////////////////////////////////////////////////////

/// Collection of handlers
struct Handlers(Vec<Box<dyn Handler>>);
impl Handlers {
    pub fn new() -> Self {
        Self(vec![])
    }

    pub fn handle<EP: 'static, F>(&mut self, func: F)
    where
        EP: Endpoint,
        F: 'static + for<'a> Fn(&'a [u8]) -> EPOut<'a, EP>,
    {
        self.0.push(Box::new(FnHandler::<EP, F>::new(func)));
    }
}

// /////////////////////////////////////////////////////////

struct MyEndpoint;
struct MyEndpointBody<'a> {
    pub string: &'a str,
}
impl Endpoint for MyEndpoint {}
impl<'a> EndpointBody<'a> for MyEndpoint {
    type Out = MyEndpointBody<'a>;

    fn serialize(body: &Self::Out) -> Vec<u8> {
        unimplemented!()
    }
    fn deserialize(raw_body: &'a [u8]) -> Self::Out {
        unimplemented!()
    }
}

// /////////////////////////////////////////////////////////

fn main() {
    let mut handlers = Handlers::new();
    handlers.handle::<MyEndpoint, _>(|_body| EPOut(MyEndpointBody {
        string: "test string",
    }));

    handlers.0[1].execute(&[]);
}

Je suis tenté de dire que c'est un bogue du compilateur Rust, étant donné que le nouveau type devrait être à peu près le même que le type associé. Il semble également y avoir des ICE liés à l'utilisation des types associés à HRTB: https://github.com/rust-lang/rust/issues/62529

Colonel trente-deux
la source
0

Pourriez-vous s'il vous plaît vérifier celui-

trait Endpoint: for<'a> DeserializeBody<'a> {}
trait DeserializeBody<'a> {
    type Out: 'a;
    fn deserialize(raw_body: &'a [u8]) -> Self::Out;
}

fn store_ep<'a, EP, F>(func: F)
where
    EP: Endpoint,
    F: 'static + Fn(&'a [u8]) -> <EP as DeserializeBody<'a>>::Out,
{
    let _ = Box::new(func);
    unimplemented!();
}

// /////////////////////////////////////////////////////////

struct MyEndpoint;
struct MyEndpointBody<'a> {
    pub string: &'a str,
}
impl Endpoint for MyEndpoint {}
impl<'a> DeserializeBody<'a> for MyEndpoint {
    type Out = MyEndpointBody<'a>;
    fn deserialize(raw_body: &'a [u8]) -> Self::Out {
        unimplemented!();
    }
}

// /////////////////////////////////////////////////////////

fn main() {
    store_ep::<MyEndpoint, _>(|raw_body| MyEndpointBody { string: "test" });
}
MaxV
la source
Ce n'est peut-être pas une solution généralisée, car Fnle paramètre de doit avoir une durée de vie arbitraire. Mais ici, cette durée de vie devient dépendante et rend ce type d'utilisation impossible, veuillez vérifier: play.rust-lang.org/…
Ömer Erden
Malheureusement, bien que cela fonctionne avec l'exemple simple, cela ne fonctionne pas avec le code que j'ai pour mon projet. Je mettrai à jour mon exemple pour mieux illustrer ce que je fais.
Colonel Thirty Two
0

Définissez DeserializeBodycomme:

trait DeserializeBody {
    type Out;
    fn deserialize(raw_body: &[u8]) -> Self::Out;
}

Outest une déclaration de type générique. Ne déclarez pas la durée de vie liée ici, elle sera explicite sur le site de définition.

À ce stade, il n'est plus nécessaire d'utiliser le trait de caractère supérieur pour Endpoint:

trait Endpoint: DeserializeBody {}

trait DeserializeBody {
    type Out;
    fn deserialize(raw_body: &[u8]) -> Self::Out;
}

Sur le site de définition, une exigence de durée de vie doit être exprimée pour le type associé Out. Si ce DeserializeBodyn'est plus un générique, il MyEndpointdoit être:

impl<'a> DeserializeBody for MyEndpoint<'a> {
    type Out = MyEndpointBody<'a>;

    ...

Et pour implémenter une telle exigence, nous pouvons recourir à un type fantôme qui nécessite toute une vie 'a.

Assembler toutes les pièces:

use core::marker::PhantomData;

trait Endpoint: DeserializeBody {}

trait DeserializeBody {
    type Out;
    fn deserialize(raw_body: &[u8]) -> Self::Out;
}

fn store_ep<EP, F>(func: F)
where
    EP: Endpoint,
    F: 'static + for<'a> Fn(&'a [u8]) -> <EP as DeserializeBody>::Out,
{
    let _ = Box::new(func);
    unimplemented!();
}

struct MyEndpoint<'a> {
    phantom: PhantomData<&'a ()>
}

struct MyEndpointBody<'a> {
    pub string: &'a str,
}

impl<'a> Endpoint for MyEndpoint<'a> {}

impl<'a> DeserializeBody for MyEndpoint<'a> {
    type Out = MyEndpointBody<'a>;

    fn deserialize(raw_body: &[u8]) -> Self::Out {
        unimplemented!();
    }
}

fn main() {
    store_ep::<MyEndpoint, _>(|raw_body| MyEndpointBody { string: "test" });
}
attdona
la source
Nan. MyEndpointBodyne peut pas emprunter raw_bodydans ce cas, car la vie anonyme de 'asurvit raw_body. L'intérêt de la HRTB est de donner raw_bodyla 'adurée de vie.
Colonel Thirty Two
Oh je vois. Avec HRTB, vous essayez de désérialiser pour toute la durée de vie, puis d'emprunter à partir des données d'entrée. Une partie de ce qui semble être une limitation du compilateur, ce qui ressemble à votre solution est serde :: DeserializeOwned et l'impl de serde ne peut emprunter aucune donnée à deserializer.
attdona
Cette solution de contournement devrait- elle fonctionner pour vous? Vec<u8>doit être alloué quelque part: déplace l'allocation vers le bas dans le deserialize.
attdona
Eh bien oui, je pourrais simplement abandonner et supprimer la durée de vie, mais je ne peux pas avoir de désérialisation sans copie et cela va à l'encontre du point de la question.
colonel trente-deux
0

Je pense que le problème est que vous demandez à vos gestionnaires de pouvoir gérer toutes les durées de vie possibles avec cette contrainte HK - que le compilateur ne peut pas prouver est vérifiée, ne pouvant donc pas faire l'équivalence MyEndpointBody <=> MyEndpoint::Out.

Si au lieu de cela, vous paramétrez vos gestionnaires pour prendre une seule vie, il semble se compiler comme requis ( lien aire de jeux ):

#![allow(unreachable_code)]

use std::marker::PhantomData;

trait Endpoint: for<'a> EndpointBody<'a> {}
trait EndpointBody<'a> {
    type Out: 'a;
    fn serialize(body: &Self::Out) -> Vec<u8>;
    fn deserialize(raw_body: &'a [u8]) -> Self::Out;
}
/// Trait object compatible handler
trait Handler<'a> {
    fn execute(&self, raw_body: &'a [u8]) -> Vec<u8>;
}

/// Wraps a function for an endpoint, convertint it to a Handler
struct FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static,
{
    func: F,
    _ph: PhantomData<EP>,
}
impl<EP, F> FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static,
{
    pub fn new(func: F) -> Self {
        Self {
            func,
            _ph: PhantomData,
        }
    }
}
impl<'a, EP, F> Handler<'a> for FnHandler<EP, F>
where
    EP: Endpoint,
    F: 'static + Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
{
    fn execute(&self, in_raw_body: &'a [u8]) -> Vec<u8> {
        let body = (self.func)(in_raw_body);
        let serialized_body = unimplemented!();
        return serialized_body;
    }
}

// /////////////////////////////////////////////////////////

/// Collection of handlers
struct Handlers<'a>(Vec<Box<dyn Handler<'a>>>);
impl<'a> Handlers<'a> {
    pub fn new() -> Self {
        Self(vec![])
    }

    pub fn handle<EP: 'static, F>(&mut self, func: F)
    where
        EP: Endpoint,
        F: 'static + Fn(&'a [u8]) -> <EP as EndpointBody<'a>>::Out,
    {
        self.0.push(Box::new(FnHandler::<EP, F>::new(func)));
    }
}

// /////////////////////////////////////////////////////////

struct MyEndpoint;
struct MyEndpointBody<'a> {
    pub string: &'a str,
}
impl Endpoint for MyEndpoint {}
impl<'a> EndpointBody<'a> for MyEndpoint {
    type Out = MyEndpointBody<'a>;

    fn serialize(body: &Self::Out) -> Vec<u8> {
        unimplemented!()
    }
    fn deserialize(raw_body: &'a [u8]) -> Self::Out {
        unimplemented!()
    }
}

// /////////////////////////////////////////////////////////

fn main() {
    let mut handlers = Handlers::new();
    handlers.handle::<MyEndpoint, _>(|_body| MyEndpointBody {
        string: "test string",
    });

    handlers.0[1].execute(&[]);
}
val
la source
Je ne comprends pas votre premier paragraphe. Vous pouvez le faire, par exemple, for<'a> Fn(&'a [u8]) -> &'a [u8]très bien, et le compilateur l'acceptera. C'est juste lorsque le type associé est renvoyé qui provoque le problème.
Colonel trente-deux
Je voulais dire que votre FnHandlerprend une fonction qui, pour chaque durée de vie possible , renvoie quelque chose. Il arrive dans votre cas que pour toute durée de vie 'a, ce sera toujours le même (a Vec<u8>), mais si vous ne le saviez pas, cette sortie pourrait dépendre de la durée de vie 'aparamétrant la fonction. Demander à cette fonction de renvoyer ce type (éventuellement dépendant de la durée de vie) pour toutes les durées de vie de l'univers est peut-être ce qui confond le compilateur: vous ne pouvez pas vérifier cette contrainte sans `` casser la localité '' et sachant que votre contrainte n'est en fait pas dépendante de la durée de vie.
val
Ce n'est pas le cas, car le wrapper newtype dans ma réponse fonctionne très bien lors de l'utilisation du type associé. Je ne pense pas que vous puissiez même avoir différents types associés pour différentes durées de vie; la seule durée de vie nommée disponible à l'échelle mondiale où vous devez mettre des implants est 'staticalors comment implémenteriez-vous des choses pour différentes durées de vie?
Colonel trente-deux