Eu tenho uma característica que tem uma função para desserializar um tipo associado. No entanto, esse tipo associado precisa ter uma vida útil que o chamador decide, portanto, eu tenho uma característica separada para a qual eu uso uma característica de classificação mais alta, para que possa ser desserializada para qualquer vida.
Preciso usar um fechamento que retorne esse tipo associado.
Eu tenho o seguinte código para fazer isso:
#![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(&[]);
}
Eu acho que deve funcionar, mas quando eu verifico, recebo um erro 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
É confuso porque MyEndpoint::Out
é um MyEndpointBody
, que eu estou retornando do fechamento, mas Rust não acha que eles são do mesmo tipo. Acho que é porque Rust escolhe vidas anônimas incompatíveis para o MyEndpointBody
tipo, mas não sei como consertar isso.
Como faço para que esse código funcione para que eu possa usar um fechamento com um tipo associado ao HRTB?
fonte
Fn
o parâmetro precisa ter uma vida útil arbitrária. Mas aqui esta vida se torna dependente e torna esse uso impossível, por favor, verifique: play.rust-lang.org/…Defina
DeserializeBody
como:Out
é uma declaração de um tipo genérico. Não declare a vida útil vinculada aqui, será explícito no site de definição.Neste ponto, não é mais necessário o limite superior do traço de nível superior para
Endpoint
:No site de definição, os requisitos de vida útil devem ser expressos para o tipo associado
Out
. SeDeserializeBody
não for mais genérico,MyEndpoint
deve ser:E para implementar esse requisito, vamos recorrer a um tipo fantasma que requer uma vida inteira
'a
.Juntando todas as peças:
fonte
MyEndpointBody
não pode pedir emprestadoraw_body
nesse caso, porque'a
sobreviveraw_body
a vida anônima da vida. O objetivo principal do HRTB é fornecerraw_body
a'a
vida útil.Vec<u8>
precisa ser alocado em algum lugar: move a alocação para odeserialize
.Acho que o problema é que você solicita que seus manipuladores sejam capazes de lidar com todas as vidas possíveis com essa restrição HK - que o compilador não pode provar é verificada, portanto, não é possível fazer a equivalência
MyEndpointBody <=> MyEndpoint::Out
.Se, em vez disso, você parametrizar seus manipuladores para que durem uma única vida, eles parecerão compilar conforme necessário ( link de playground ):
fonte
for<'a> Fn(&'a [u8]) -> &'a [u8]
muito bem, e o compilador aceitará. É apenas quando o tipo associado é retornado que causa o problema.FnHandler
assume uma função que, para toda vida possível , retorna algo. Ocorre no seu caso que, para qualquer vida útil'a
, sempre será a mesma (aVec<u8>
), mas se você não soubesse disso, essa saída poderá depender da vida útil que'a
parametriza a função. Solicitar que a função retorne esse tipo (possivelmente dependente da vida) para todas as existências no universo é possivelmente o que confunde o compilador: você não pode verificar essa restrição sem 'quebrar a localidade' e sabendo que sua restrição não é realmente dependente da vida.'static
como você implementaria coisas para diferentes vidas?