Enums and TryAsRef

I'm looking at the thread about TryFrom in the repo, specifically this comment on TryAsRef, and think it fits a use-case I have. Consider the following code:


struct Name(Vec<u8>); // (assume From/Into<Vec<u8>>)
struct Comment(Vec<u8>);

enum Token {
    String(String),
    Char(char),
    Name(Name),
    Comment(Comment),
    Number(f64),
    Integer(i64),
    // ...
}

impl Token {
    pub fn downcast_string(self) -> Option<String> /* or Result<String, Token> */ {
        match self {
            Token::String(s) => Some(s),
            _ => None
        }
    }

    // pub fn downcast_char(self) -> Option<char> { ...

    // ...

    pub fn try_as_string(&self) -> Option<&String> {
        match self {
            &Token::String(ref s) => Some(s),
            _ => None,
        }
    }

    // ...
}

This seems to match the TryFrom trait in unstable, and the (as yet as far as I am aware) non-existent trait TryAsRef. It would tidy up the implementation by moving (2 * variants) new functions into some standard-looking impls, which would be easy to make macros to implement.

Does this suggest that there should be both TryFrom and TryAsRef traits in libstd, or have I missed something :slight_smile: ?

TryFrom returns a Result, so it's really meant to signal a failure during conversion. I suppose if you really squint your enum case might be considered as such, but it's really not a failure, I think. Having said that, you could create a impl<'a> TryFrom<&'a Token> for &'a String.

I'm not sure proliferation of traits is a great thing, and a TryAsRef seems kind of niche. You can always create your own trait to do that.

The reason I like shared traits is it gives the same name to the same pattern. Say I want an optional value, I will use Option<T>, rather than my own type duplicating functionality. Also, it takes less headspace to recognise the impl<T, U> From<T> for U pattern means I can convert from T to U, than to search through the documentation for some fn from_my_t(t: T) -> U.

I guess you should do if let &Comment(ref comment) = token { (and the equivalent for moving) - I'm just kinda used to functional patterns like maps :slight_smile: .

I'm with you on reusing common types/abstractions. I'm just not sure your case is common/general enough to warrant a stdlib type (trait). If you imagine functions that take a hypothetical TryAsRef, what would they do in the None case? Nothing? The (try) conversion traits return a Result to signal that the conversion failed as that may signal a true problem that needs to be handled. In the Option case, it's unclear what the general semantics should be. To that end, you can write your own functions that take an Option explicitly, pick the appropriate semantics for None, and then use your own APIs to call them appropriately.

So, to paraphrase: "In this case it's obvious that a None means that the enum is in another state, but in general it's not obvious what None means, and so it's less confusing to use a custom function that describes what None means.", is this what you mean?

Yeah, pretty much. Also the callee doesn't know that your TryAsRef is backed by enum - it just sees Some(...) and None. It's hard to ascribe meaning to the None case, in general - and for this to be a stdlib concept, it has to generalize well. If the function just does nothing for None, then it's just obscuring the true functionality - you, as the caller, could avoid calling the function with None to begin with.

1 Like