Wie kann ich einen zugeordneten Typ von einem höherrangigen Merkmal zurückgeben?

11

Ich habe eine Eigenschaft, die eine Funktion zum Deserialisieren eines zugeordneten Typs hat. Dieser zugeordnete Typ muss jedoch eine Lebensdauer haben, über die der Anrufer entscheidet. Daher habe ich ein separates Merkmal, für das ich ein höherrangiges Merkmal verwende, damit es für jedes Leben deserialisiert werden kann.

Ich muss einen Abschluss verwenden, der diesen zugeordneten Typ zurückgibt.

Ich habe den folgenden Code, um das zu tun:

#![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(&[]);
}

Ich denke, das sollte funktionieren, aber wenn ich es überprüfe, erhalte ich einen Tippfehler:

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 ist verwirrend, weil MyEndpoint::Outes eine ist MyEndpointBody, die ich von der Schließung zurückkehre, aber Rust glaubt nicht, dass sie vom gleichen Typ sind. Ich vermute, das liegt daran, dass Rust inkompatible anonyme Lebensdauern für den MyEndpointBodyTyp auswählt, aber ich weiß nicht, wie ich das beheben soll.

Wie kann ich diesen Code zum Laufen bringen, damit ich einen Abschluss mit einem HRTB-zugeordneten Typ verwenden kann?

Oberst zweiunddreißig
quelle

Antworten:

4

Wenn der Abschluss den Rückgabetyp in einen neuen Typ einschließt, wird das Problem behoben:

#![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(&[]);
}

Ich bin versucht zu sagen, dass dies ein Rust-Compiler-Fehler ist, wenn man bedenkt, dass der Newtype ungefähr dem entsprechenden Typ entsprechen sollte. Es scheint auch einige ICEs zu geben, die sich auf die Verwendung von HRTB-assoziierten Typen beziehen: https://github.com/rust-lang/rust/issues/62529

Oberst zweiunddreißig
quelle
0

Könnten Sie bitte überprüfen , dass ein

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
quelle
Dies ist möglicherweise keine verallgemeinerte Lösung, da Fnder Parameter eine beliebige Lebensdauer haben muss. Aber hier wird dieses Leben abhängig und es macht diese Art einer Verwendung unmöglich, bitte überprüfen Sie: play.rust-lang.org/…
Ömer Erden
Obwohl dies mit dem einfachen Beispiel funktioniert, funktioniert es leider nicht mit dem Code, den ich für mein Projekt habe. Ich werde mein Beispiel aktualisieren, um besser zu veranschaulichen, was ich tue.
Oberst Zweiunddreißig
0

Definieren DeserializeBodyals:

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

Outist eine Deklaration eines generischen Typs. Erklären Sie hier nicht, dass die Lebensdauer gebunden ist, dies wird auf der Definitionsseite explizit angegeben.

Ab diesem Zeitpunkt ist das höherrangige Merkmal nicht mehr erforderlich für Endpoint:

trait Endpoint: DeserializeBody {}

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

An der Definitionsstelle muss eine Lebensdaueranforderung für den zugehörigen Typ angegeben werden Out. Wenn DeserializeBodyes nicht mehr ein Generikum ist, MyEndpointmuss es sein:

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

    ...

Um solche Anforderungen zu implementieren, können Sie auf einen Phantomtyp zurückgreifen, der eine lebenslange Lebensdauer erfordert 'a.

Alle Teile zusammenfügen:

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
quelle
Nee. MyEndpointBodykann raw_bodyin diesem Fall nicht ausleihen , da die anonyme Lebensdauer 'aüberlebt raw_body. Der gesamte Punkt des HRTB ist es, raw_bodydie 'aLebensdauer anzugeben .
Oberst Zweiunddreißig
Oh, ich verstehe. Mit HRTB versuchen Sie, ein Leben lang zu deserialisieren und anschließend aus den Eingabedaten zu leihen. Ein Teil, der eine Einschränkung des Compilers zu sein scheint und wie Ihre Lösung aussieht, ist serde :: DeserializeOwned, und der serde impl kann keine Daten vom Deserializer ausleihen.
Attdona
Sollte diese Problemumgehung für Sie funktionieren? Vec<u8>muss irgendwo zugeordnet werden: verschiebt die Zuordnung nach unten in die deserialize.
Attdona
Nun ja, ich könnte einfach aufgeben und die Lebensdauer entfernen, aber dann kann ich keine Nullkopie-Deserialisierung haben und es besiegt den Punkt der Frage.
Oberst Zweiunddreißig
0

Ich denke, das Problem ist, dass Sie Ihre Handler auffordern, alle möglichen Lebensdauern mit dieser HK-Einschränkung zu bewältigen - was der Compiler nicht nachweisen kann, ist verifiziert und daher nicht in der Lage, die Äquivalenz herzustellen MyEndpointBody <=> MyEndpoint::Out.

Wenn Sie stattdessen Ihre Handler so parametrisieren, dass sie eine einzige Lebensdauer haben, scheint sie nach Bedarf zu kompilieren ( Spielplatz-Link ):

#![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
quelle
Ich verstehe Ihren ersten Absatz nicht. Sie können zum Beispiel for<'a> Fn(&'a [u8]) -> &'a [u8]ganz gut machen, und der Compiler wird es akzeptieren. Nur wenn der zugehörige Typ zurückgegeben wird, verursacht das Problem.
Oberst Zweiunddreißig
Ich meinte, dass Sie FnHandlereine Funktion übernehmen, die für jedes mögliche Leben etwas zurückgibt. In Ihrem Fall ist es für jede Lebensdauer 'aimmer dieselbe (a Vec<u8>). Wenn Sie dies jedoch nicht wussten, hängt diese Ausgabe möglicherweise von der Lebensdauer ab 'a, die die Funktion parametrisiert. Das Anfordern dieser Funktion, diesen (möglicherweise lebenszeitabhängigen) Typ für alle Lebensdauern im Universum zurückzugeben, verwirrt den Compiler möglicherweise: Sie können diese Einschränkung nicht überprüfen, ohne die Lokalität zu brechen und zu wissen, dass Ihre Einschränkung tatsächlich nicht lebensdauerabhängig ist.
Val
Dies ist nicht der Fall, da der Newtype-Wrapper in meiner Antwort bei Verwendung des zugehörigen Typs einwandfrei funktioniert. Ich glaube nicht, dass Sie für verschiedene Lebenszeiten unterschiedliche Typen haben können. Die einzige benannte Lebensdauer, die im globalen Bereich verfügbar ist, in dem Sie Impls platzieren müssen, ist. 'staticWie würden Sie also Dinge für verschiedene Lebensdauern implementieren?
Oberst Zweiunddreißig