Tengo un rasgo que tiene una función para deserializar un tipo asociado. Sin embargo, ese tipo asociado debe tener una vida útil que decida la persona que llama, por lo que tengo un rasgo separado para el que uso un rasgo de mayor rango, para que pueda ser deserializado para cualquier vida.
Necesito usar un cierre que devuelva este tipo asociado.
Tengo el siguiente código para hacer eso:
#![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(&[]);
}
Creo que debería funcionar, pero cuando lo reviso me sale un error de tipo:
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
Es confuso porque MyEndpoint::Out
es una MyEndpointBody
, que estoy volviendo del cierre, pero Rust no cree que sean del mismo tipo. Supongo que es porque Rust elige vidas anónimas incompatibles para el MyEndpointBody
tipo, pero no sé cómo solucionarlo.
¿Cómo puedo hacer que este código funcione para poder usar un cierre con un tipo asociado HRTB?
fuente
Fn
el parámetro debe tener una vida útil arbitraria. Pero aquí esta vida se vuelve dependiente y hace que este tipo de uso sea imposible, consulte: play.rust-lang.org/…Definir
DeserializeBody
como:Out
es una declaración de tipo genérico. No declare el límite de por vida aquí, será explícito en el sitio de definición.En este punto ya no es necesario el rasgo de rango superior para
Endpoint
:En el sitio de definición, se deben expresar los requisitos de por vida para el tipo asociado
Out
. SiDeserializeBody
no es más un genérico,MyEndpoint
debe ser:Y para implementar dicho requisito, recurramos a un tipo fantasma que requiere toda una vida
'a
.Poniendo todas las piezas juntas:
fuente
MyEndpointBody
no puedo pedir prestadoraw_body
en ese caso, porque'a
sobreviveraw_body
la vida anónima de la vida. El punto de la hrtB entero es para darraw_body
la'a
vida.Vec<u8>
debe asignarse en algún lugar: baja la asignación aldeserialize
.Creo que el problema es que solicitas a tus manejadores que puedan manejar todas las vidas posibles con esa restricción HK, que el compilador no puede probar, se verifica y, por lo tanto, no puede hacer la equivalencia
MyEndpointBody <=> MyEndpoint::Out
.Si, en cambio, parametrizas tus manejadores para que tomen una sola vida útil, parece que se compila según sea necesario ( enlace de patio de recreo ):
fuente
for<'a> Fn(&'a [u8]) -> &'a [u8]
bien, y el compilador lo aceptará. Es justo cuando se devuelve el tipo asociado que causa el problema.FnHandler
toma una función que, para cada vida posible , devuelve algo. Ocurre en su caso que durante toda la vida'a
, siempre será lo mismo (aVec<u8>
), pero si no lo sabía, esa salida podría depender de la vida útil que'a
parametriza la función. Solicitar que la función devuelva ese tipo (posiblemente dependiente de la vida útil) para todas las vidas en el universo es posiblemente lo que confunde al compilador: no puede verificar esta restricción sin 'romper la localidad' y saber que su restricción no depende realmente de la vida útil.'static
entonces, ¿cómo implementaría cosas para diferentes vidas?