How can/should I avoid saying "result" twice in "Result<ParseResult, Error>"?

Rust newbie here (longtime programmer though). This is more a question about what is a canonical way to do "this sort of thing" "in Rust" rather than directly a howto question.

As a learning exercise, I'm translating some code found in an old CS whitepaper from standard ML into Rust. The original functional code used tuples,

(a_value, rest_of_tokens)

as a return type from parsing functions. I found it aided my comprehension to create a helper type for this:

struct ParseResult<'a, T> {
value : T,
rest : &'a [Token],
}

(Side note: rest here might need to become &'a [Token<'a>] where 'a is the lifetime of the str being parsed, and then I'm not sure if that can be 'a twice or if I need two lifetimes, 'a and 'b. But I don't want that to sidetrack my topic question.)

Now the thing is if I want/need parsing to handle failure I might also use Result<T,E>, but as a function return type that becomes partly redundant in both naming and purpose:

Result<ParseResult, Error>

Hmmm, another side note; as I write this I'm wondering if it should rather be:

ParseResult<Result<T,Error>>

This second construction has the benefit of a failed parse being able to still return rest of tokens, however in practice the parse function handles an unsuccessful parse by trying the next choice on fn parse's original input tokens slice; it does not do it by chaining off the result of the previous failed parse choice.

There would, however, be no ambiguity and no naming redundancy if my ParseResult type could also be a Result<T,E>. Either by ParseResult implementing some trait, and/or turning ParseResult into an enum. Is it possible, technically, to integrate this with Rust's built-ins around Result<T,E>? Is it a good idea to implement this this way?

Or, should I just remove the word "result" from the name of type "ParseResult"?

While

Result<Parse, Error>

is in itself clear and succinct, I'm not sure I like "struct Parse" as a name/type standing on its own, and I don't like that the same name, "parse/Parse" is both the name of a function and a type. (Maybe that's fine and I should update my opinion.)

I could use:

Result<Parsing, Error>

which is at least as, if not more, clear about what the result is from. This:

struct Parsing

is a bit weird to have a type ending in "ing", but maybe it's just weird enough that the reader goes, "Hmmm what's going on here? Ah I see: it doesn't stand by itself; it's used in Result<Parsing, Error>."

(This is low-stakes in toy/learning exercise code, but I'm trying to calibrate my sensibilities.)

I would perhaps be inclined to just use a tuple and consider the type Result<(T, &'a [Token]), E> as your "parse result" and live with the fact that the tuple elements don't have names. The reason this particular type is difficult to name is that it contains two separate things, namely the output and the new state of the input. This is par for the course for functional programmers, of course. Perhaps that's why they don't like naming things?

Alternatively, one trick for naming difficult-to-name helper types is to literally call them what they are. In this case, ValueAndRest. Sounds silly at first, but it works surprisingly well and makes code very readable.

1 Like

You might also choose to have an alias for the common return type:

struct ValueAndRest<'a, T> {...}

type ParseResult<'a, T> = Result<ValueAndRest<'a, T>, Error>;

This way the type is a Result (so ? etc. can still be used), and the type name acknowledges that, but you don't need to separately name the success type most of the time. (Of course, this might also make the code less comprehensible along with being more concise. It depends on how it is used.)

1 Like

Actually I think if I hide the tuple in the type alias, that gives me 90% of what I wanted, and I don't have to name struct ValueAndRest. The code I'm translating is full of functions that return functions (closures) that return functions that return the tuple. Some of the intermediate functional steps also accept a tuple (possibly of different value type) as an argument, so it becomes very confusing.

But that also means the only place I need the named type is in the signature of these parse functions, for the purpose of breaking up the line noise of parens and commas with some names.