Search code examples
rustserde

How to unpack a either::Either by the contained type


Can I unpack a either::Either by the contained type, not by side?

Say I have a Either<u8, f32>, and want to either get None or a value with the desired type, without knowing with side the value is in.

I've tried doing it with macros like this:

/// Unpack a 2 option item
#[macro_export]
macro_rules! unpack_dual {
    ( $item: expr, $item_t: ty ) => {
        match $item.inner {
            either::Either<$item_t, _> => {
                if let either::Right(item) => $item.inner {
                    Some(item)
                } else {
                    None
                }
            }
        },
        either::Either<_, $item_t> => {
            if let either::Left(item) = $item.inner {
                Some(item)
            } else {
                None
            }
        }
    };
}

where you pass the value and the desired type, but It doesn't work because you can't match on the Either shape.

This is for ease of use with a configuration file parser where there are fields that may be one of 2 things it uses Either.

Like in this example struct, using structstruck for nesting and serde for deserialization.

use std::collections::HashMap;
use either::Either;

extern crate structstruck;

use serde::{Deserialize};
use serde_yaml as yaml;

structstruck::strike! {
    #[derive(Debug, Deserialize)]
    struct Example {
        pub dual_field: HashMap<String,
                              #[derive(Debug, Deserialize)]
                              #[serde(transparent)]
                              pub struct TypeAOrTypeB {
                                  #[serde(with = "either::serde_untagged")]
                                  pub inner:
                                  Either<#[derive(Debug, Deserialize)]
                                         pub struct TypeA {
                                             // -- snip --
                                         },
                                         #[derive(Debug, Deserialize)]
                                         pub struct TypeA {
                                             // -- snip --
                                         }>>
                              }
   // -- snip ---
}


Solution

  • The best way I can come up with that uses only std is via Any which restricts this approach to types that are 'static.

    use either::Either::{self, *};
    use std::any::Any;
    
    fn unpack_dual<L: 'static, R: 'static, T: 'static>(e: &Either<L, R>) -> Option<&T> {
        let e: Either<&dyn Any, &dyn Any> = e.as_ref().either(|l| Left(l as &dyn Any), |r| Right(r as &dyn Any));
        e.either(|l| l.downcast_ref::<T>(), |r| r.downcast_ref::<T>())
    }
    
    
    fn main() {
        let e: Either<u8, f32> = Right(3.1415);
        let maybe_u8: Option<&u8> = unpack_dual(&e);
        let maybe_f32: Option<&f32> = unpack_dual(&e);
        let probably_not_string: Option<&String> = unpack_dual(&e);
    
        assert_eq!(maybe_u8, None);
        assert_eq!(maybe_f32, Some(&3.1415));
        assert_eq!(probably_not_string, None);
    }