3Hren/msgpack-rust

Better configuration ergonomics

piegamesde opened this issue · 4 comments

I find setting a desired configuration rather tedious. The following things would help:

  • Make all constructors const so that they may be used in constants
  • Provide some constants for common use cases. I suggest having one for the most compact configuration (I think this is just DefaultConfig) and one that is fully self-describing
  • If breaking backwards-compatibility is an option, I'd design the structs differently: Maybe have one struct for the configuration, with one generic parameter for each option: Config<HumanReadable = True, Struct = Map, Variant = String> or something. Maybe those type-level constructs could be improved with const generics in the future.

I'm happy to accept PRs that improve it. const fn is definitely a good idea.

I'm not sure about separate generic arguments. Can you experiment with that and check if it really works, and there aren't gotchas with it? e.g. I'm worried that struct True may look weird and unclear out of context, and {true} isn't great either.

I did a quick prototype of what I had in mind. Type level programming is always a bit weird, so it took me a few attempts to find names that weren't straight up horrible.

use std::marker::PhantomData;

trait IsHumanReadable {}
struct HumanReadable;
impl IsHumanReadable for HumanReadable {}
struct NotHumanReadable;
impl IsHumanReadable for NotHumanReadable {}

trait StructSerialization {}
struct StructAsMap;
impl StructSerialization for StructAsMap {}
struct StructAsTuple;
impl StructSerialization for StructAsTuple {}

trait EnumSerialization {}
struct VariantAsInteger;
impl EnumSerialization for VariantAsInteger {}
struct VariantAsString;
impl EnumSerialization for VariantAsString {}

struct Config<
    R: IsHumanReadable = NotHumanReadable,
    S: StructSerialization = StructAsTuple,
    V: EnumSerialization = VariantAsInteger
>(PhantomData<(R, S, V)>);

impl <R: IsHumanReadable, S: StructSerialization, V: EnumSerialization> Config<R, S, V> {
    // Cannot be `const` yet
    fn new() -> Config<R, S, V> {
        Config(PhantomData)
    }
}

const COMPACT_BINARY: Config = Config(PhantomData);
const SELF_DESCRIBING_BINARY: Config<NotHumanReadable, StructAsMap, VariantAsString> = Config(PhantomData);
const SELF_DESCRIBING_HUMAN: Config<HumanReadable, StructAsMap, VariantAsString> = Config(PhantomData);

While doing this, I got put off by the human readable setting multiple times, so I looked it up, and now I'd like to know on which occasions would one ever want to use it? As far as I can tell it has no impact on whether the resulting format is self-describing or not, so what would then be the point to serialize things as String instead of numbers in a format that's binary anyways?

I just noticed that this also affects the Serializer struct too, since at the moment it does not expose a way to directly set the configuration other than its methods. Alternatively, simply hiding the config module from the docs may actually be enough …

human_readable is a Serde thing. IIRC Serialize/Deserialize can use it to decide how to represent data.