简体   繁体   中英

how to erase impossible variant from enum?

The setting: In a web server, the business code needs to access the request payload, which is located in either query or body, but not both, the deserialization of query is handled by a trait Q , and the deserialization of the body is handled by the trait B != Q , a middleware, f , (a piece of code that sitting between the web framework and business code) is responsible for the invocation of Q and B according to the request method, if it is a GET, then invoke Q, if it is a POST then invoke B, thus the signature of f must be like f<Query: Q, Body: B>(...) -> Payload<Query, Body> , "must be" because somewhere in the implementation of f :

let payload = if request_method == "GET" {
    let query: Query = ...::deserialize(request_query);
    Payload::Query(query)
} else if request_method == "POST" {
    let body: Body = ...::deserialize(request_body);
    Payload::Body(body)
} else {
    // error
}

...

payload

(sadly, I didn't find a way to tell the framework to call f<Q> or f<B> , nor a way to unify Q and B into a single trait)

In the business code, the handler for a GET would have the signature handler(Payload<Query, ()>)... and a POST would have handler(Payload<(), Body>)

The question: is there some technique to erase the unit type from the payload without turning it into a trait (trait is difficult to work with)? (The real rationale for erasing the unit type is actually something else, but let's just say I want the unit type erased)

Edit: the meaning of "erasing" is to turn Payload<Query, Body> to Query or Body , ie handler(Payoad<(), Body>) would become handler(Body) - the "useless" unit variant of Payload<_, _> erased, resulting a enum with only one variant, essentially the "inner" itself.

Yes, there is a way to encode in Rust's type system that a variant of an enum can never be user. You have to use empty types (also called void types). One such type (two actually, but whatever) is already present in the prelude, it's the never type ! . Since it's not completely clear about where you want to use it, I'll explain with the Result type.

Assume you have a trait which requires returning a Result<T, U> where the implementer of the trait can choose which T and U . For example, TryFrom :

struct A;
struct B;

impl TryFrom<A> for B {
    type Error = ...;
    fn try_from(a: A) -> Result<B, Self::Error> {
        Ok(B)
    }
}

In this example, it's clear that this conversion cannot fail. So what should I put in place of the dots? Often people put the unit type () there, thinking that is carries no information (because there is a single value possible of that type) so it's perfect for the job. However, this is not exactly what we wanted, right? There is actually a value of the type () , so this doesn't mean it's impossible to return Err(()) . What we want instead is a type of which there are no values, because in that case we are sure it's impossible to return a value of that type. So the correct implementation would be

impl TryFrom<A> for B {
    type Error = !;
    ...
}

In this case, there is even a RFC that proposes the following code to Just Work:

let Ok(b) = B::try_from(A);

This would be accepted, because the compiler knows it cannot be an Err(something) , because then something: ! which is impossible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM