"Unreachable pattern" when matching on result of deserialized struct

I've got code which is serializing structs, sending them over the wire, and then deserializing them, and then having to do different things based on the type of the struct. I'm using a match, but keep running into "unreachable pattern" with a catchall pattern at the end.

Here's a simplified version of my code:

use serde::{Serialize, Deserialize, de::DeserializeOwned};
use serde_json;

#[derive(Serialize, Deserialize)]
struct Foo {
    a: i32,
    b: u8,
}

#[derive(Serialize, Deserialize)]
struct Bar {
    c: i32,
    d: u8,
}

fn send<S: Serialize>(s: &S) -> Result<String, serde_json::Error> {
    serde_json::to_string(s)
}

fn recv<D: DeserializeOwned>(json: &str) -> Result<D, serde_json::Error> {
    serde_json::from_str(json)
}

fn main() {
    let f = Foo { a: 1, b: 2 };
    let json = send(&f).unwrap();
    let deserialized = recv(&json).unwrap();
    match deserialized {
        Foo { a, b } => println!("a = {}, b = {}", a, b),
        _ => println!("who knows?"),
    };
}

When I compile this, I get this error:

warning: unreachable pattern
  --> src/main.rs:30:9
   |
29 |         Foo { a, b } => println!("a = {}, b = {}", a, b),
   |         ------------ matches any value
30 |         _ => println!("who knows?"),
   |         ^ no value can reach this
   |
   = note: `#[warn(unreachable_patterns)]` on by default

Playground link.

When I make f a Bar instead of a Foo, the unwrap() on the let deserialized = line panics because it thinks it's supposed to be a Foo, which means that the compiler's inferring the type of the object based on the match pattern (or, at least it seems like it is).

What I'm trying to do here is to, basically, deserialize a message into it's original message type, and then, if it's a message I'm expecting at that point, do something with it, otherwise throw an error. I would have thought this would be the proper place to use match, but clearly I'm mistaken. How would one go about doing what I'm trying to do?

Much appreciation for any help with this.

-Jack

Rust is strictly and statically typed. So deserialized has to have a single type, and this makes no sense for example:

    match deserialized {
        Foo { a, b } => println!("a = {}, b = {}", a, b),
        Bar { c, d } => println!("c = {c}, d = {d}"),
        _ => println!("who knows?"),
    };

And yes, the compiler is inferring the type of deserialized based on the (first) match pattern. There's no way to infer it from dynamic string data (or whatever) at compile time.

You could attempt multiple deserializations:

    if let Ok(deserialized) = recv::<Foo>(&json) {
        println!("a = {}, b = {}", deserialized.a, deserialized.b);
    } else if let Ok(deserialized) = recv::<Bar>(&json) {
        println!("c = {}, d = {}", deserialized.c, deserialized.d);
    } else {
        println!("who knows?");
    };

But it'd probably be nicer to create an enum with a variant for every potentially expected type, and deserialize to (and match on) that.

Or perhaps someone has a better, higher-level suggestion.

2 Likes

The match expression is the same as:

    let Foo { a, b } = recv(&json).unwrap();
    println!("a = {}, b = {}", a, b);

If that's what you meant, then great!

I would probably simplify the above if-let-chain slightly:

    if let Ok(Foo { a, b }) = recv(&json) {
        println!("a = {a}, b = {b}");
    } else if let Ok(Bar { c, d }) = recv(&json) {
        println!("c = {c}, d = {d}");
    } else {
        println!("who knows?");
    }

But it's probably as good as you can get without going full duck-typing with serde_json::Value.

1 Like

I would probably simplify the above if-let-chain slightly:

    if let Ok(Foo { a, b }) = recv(&json) {
        println!("a = {a}, b = {b}");
    } else if let Ok(Bar { c, d }) = recv(&json) {
        println!("c = {c}, d = {d}");
    } else {
        println!("who knows?");
    }

FWIW, that doesn't seem to work either. This:

fn main() {
    let f = Bar { c: 1, d: 2 };
    //let f = Foo { a: 1, b: 2 };
    let json = send(&f).unwrap();
    let deserialized = recv(&json);
    if let Ok(Foo { a, b }) = deserialized {
        println!("a = {}, b = {}", a, b);
    }
    else if let Err(error) = deserialized {
        println!("Error: {}", error);
    }
    else {
        println!("who knows?");
    };
}

Gives me:

Error: missing field `a` at line 1 column 13

which means it's still inferring based on the first if let statement.

Yeah, I think an enum's probably what I'm gonna have to do. Not thrilled, but it's probably the best alternative. Thanks!!

You changed the code to bind a single call to a variable. That is not going to work. You can use the code as I wrote it. It must be written in this form where multiple attempts are made to deserialize, as described by @quinedot.

That's fair, but in my case I'm simulating reading from a network connection with my recv call, so doing it your way would involve making two separate network reads, which isn't what I want.

Store the JSON locally as a string of some sort, and repeatedly try to deserialize that instead.

1 Like

Yes, that code is pretty much the same as

where you're trying to put values of two different types into one variable.

This signature:

fn recv<D: DeserializeOwned>(json: &str) -> Result<D, serde_json::Error> {
    serde_json::from_str(json)
}

says that the caller chooses D -- the type to deserialize to. The choice is made before calling the function. If you need to attempt multiple deserializations but only call recv once, you'll have to change the API to decouple network-reading and deserialization.

Or if you go the enum route you'll only need to deserialize once. But the API would still change because the caller will "choose" by matching on the enum.

fn recv(json: &str) -> Result<TheEnum, serde_json::Error> {
    serde_json::from_str(json)
}
3 Likes

Right, it wasn't "my way". I was just providing a minor improvement over the earlier suggestion.

Fair, and I do appreciate the suggestion. :+1: