Tagged fields in Rust?


#1

I’ve been wondering how to implement a clean XDR codec in Rust. The thing that’s been vexing me is how to map from a Rust enum into an XDR switch - specifically, how to represent the mapping to an XDR enum (which is simply a symbolic name for an int, like in C).

Go has the concept of “tags” for structure fields, which is useful for adding metadata. Rust’s attribute mechanism seems to be a way of achieving something similar.

For example:

enum Thing {
    // associate a static str with an enum field
    #[tag("xdr: enum=44")]
    Foo(u32),
    #[tag("xdr: enum=33")]
    Bar(String),
}

Go makes the tags visible at runtime via its reflection interface, but as Rust doesn’t have reflection it would need some other mechanism. I don’t see any precedents for something analogous, but I guess its a little like sizeof (ie, querying a compile-time attribute of a type), so it would be exposed as a compiler intrinsic function with a signature something like: … well, I don’t know what it would look like. I can’t think of a way of referring to a field except by name. This, maybe?

// Get a tag for a field of type T
fn get_tag<T>(field: &str) -> Option<&'static str>

Even assuming this is a reasonable way to solve the problem, I’m not sure what its full semantics are. Should it be defined for all types T, or just struct/enum? What if the field doesn’t exist - it would be annoying to have a runtime error, or a silent failure because the field no longer exists, was typoed, or renamed.

Maybe I’m barking up the wrong tree with this. Perhaps it would be better to define an auxillary trait for Encodable/Decodable that can return extra metadata, and then have a specific Encoder/Decoder call that trait to get info. (Or just add it to Encodable/Decodable.)

Thoughts?


#2

Why wouldn’t something like this suffice?

impl Thing {
    fn get_xdr_thing(&self) -> u32 {
        match *self {
            Thing::Foo(..) => 44,
            Thing::Bar(..) => 33,
            ...
        }
    }
} 

You could also make a syntax extension to automatically generate that method if you didn’t want to have a manual implementation:

#[make_xdr_variant]
enum Thing {
    #[xdr_variant = "44"]
    Foo(u32),
    #[xdr_variant = "33"]
    Bar(String),
    ...
}

#3

I think there is something more useful than just specific to xdr. One place I’ve found Go’s tags handy is with json encoding. Sometimes programs will encode numbers as strings, and other times as numbers. For buggy apps, it is necessary to get this right. In other circumstances, it is helpful to have control over the json field names, especially if they don’t form valid Rust identifiers.

Another example is xml, where xml doesn’t really map to structures directly, but many xml trees can be mapped, with a little extra guidance.

It seems like this information would be useful to the Endoder and Decoder traits, especially when enountering structs.

I don’t think we want to do it in the very dynamic way that Go does, but I suspect without being able to add at least some kind of data to the fields of a struct, it will result in lots of boilerplate code being written.


#4

This doesn’t scale well. The trouble is that it’s an N x M problem: N types need encoding, and M different encodings. Not every type will need special encoding info, and not every encoding needs special info.

Normally types are not encoded in multiple completely different encodings; you generally make up a specific type to represent some kind of data, then translate into and out of one or two encodings - so it’s not really a burden to tag them with coding-specific info. The only reason there’s any motivation to make a somewhat generic solution is to take advantage of the compiler’s #[derive(RustcEncodable)] support.

However, that does make the answer to my question pretty obvious. The code the compiler generates for Encodable/Decodable could simply pass an tag: Option<&'static str> to encode*.