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::Out
c'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 MyEndpointBody
type, 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?
la source
Fn
le 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/…Définissez
DeserializeBody
comme:Out
est 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
:Sur le site de définition, une exigence de durée de vie doit être exprimée pour le type associé
Out
. Si ceDeserializeBody
n'est plus un générique, ilMyEndpoint
doit être: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:
la source
MyEndpointBody
ne peut pas emprunterraw_body
dans ce cas, car la vie anonyme de'a
survitraw_body
. L'intérêt de la HRTB est de donnerraw_body
la'a
durée de vie.Vec<u8>
doit être alloué quelque part: déplace l'allocation vers le bas dans ledeserialize
.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 ):
la source
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.FnHandler
prend 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 (aVec<u8>
), mais si vous ne le saviez pas, cette sortie pourrait dépendre de la durée de vie'a
paramé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.'static
alors comment implémenteriez-vous des choses pour différentes durées de vie?