Just a pondering.on result<(),T>

My point still stands. While they are symmetrical in a structural sense (i.e. you'll get an equivalent program after swapping Ok/Err and changing all relevant methods, modulo ?), they have a drastically different meaning for humans. And this difference matters a lot. Remember that we write code to be understood by fellow humans. Thus how types are perceived are equally (or even more) important compared how they are viewed by compiler and represented in machine. And we actively use the difference in meaning between Ok and Err when we write code and design APIs.

If you'll use Result to communicate two different "happy" results instead of defining domain specific enum, I am more than sure that most of those who will read this code will be confused as hell.

No, it's not only about valid operations. It's also about in which contexts this type expected to be used. For example the following functions will be really confusing for most of us: measure_duration() -> Meter, get_area() -> Meter, sin(theta: Radian) -> Meter.

BTW this is why I really dislike SystemTime::duration_since method.

1 Like

Remember that we write code to be understood by fellow humans. Thus how types are perceived are equally (or even more) important compared how they are viewed by compiler and represented in machine.

It's important to matters of word choice and naming conventions, but it is not important when we're talking about what information is needed to correctly understand what the code actually does. If decision X is contingent upon fact Y, then it doesn't matter what words you use for those concepts, you still need knowledge of Y to understand X. Alternatively, the convention that Ok means things are going well doesn't actually establish that your program is behaving correctly just because it produces and Ok value. Conversely, if I write a parser which happens to return Err, this doesn't imply that something 'unhappy' has happened, only that I need to move on to another parse rule. This is not an error state or an exception, it is normal operation.

It’s also about in which contexts this type expected to be used.

Normally, I would be 100% with you on this matter. But normally, I would only say such a thing when we're talking about establishing the meanings of words in natural language. In a formal language, the meaning of syntax is established by where it is used, not where it is expected to be used. (Unless we're talking about the expectations of a compiler or some such -- when producing compile errors etc..)

1 Like

I am fine with methods like report_error(err: Err1) -> Result<(), Err2>, but I expect that if function has returned Ok, then associated action has finished without problems, i.e. function has followed a "happy" path, even if this action lies in a catastrophic branch of the program.

As for parser, if it returns Err, then I expect that parsing has failed and I don't need to look into the parsing code to understand why it happened. "Unhappiness" is local to the function which we execute, e.g. if we first try to parse JSON and if it fails we continue with XML, then failure of parse_json means that our initial assumption about data being JSON was wrong, so parse_json is "unhappy", but parse_xml could finish "happily", thus resulting in a "happy" execution of the program, but it does not change the fact that parse_xml was "unhappy". (damn, these analogies...) Of course we can bubble this "unhappiness", but it's optional and has nothing to do with semantics of Result.

1 Like

That's what I've been trying to say. It's context dependent. If I write a parser which returns Ok(()) when the parse fails and Err(&str) when it succeeds, then what have I done wrong? All I've done is make it easy to propagate parse success rather than parse failure, but Ok and Err have taken on opposite meanings. You could make a parser which 'digs' for a match like this. It's just a different program with different behavior.

Ok/Err is Success/Failure. A successful parse is Ok. A failed parse is Err.

This is what "happy"/"unhappy" refers to in this case. Result carries this semantic meaning. If it's not a success/failure case where the Try is accurate, Either (or a domain specific enum) should be used.

2 Likes

You wrote a really confusing piece of code. Personally I will be really angry if I'll stumble upon such code. See my earlier post about programs being written for humans to be understood.

1 Like

You don't get to tell me which outputs of my program are 'success' or not; I tell you that.

You wrote a really confusing piece of code. Personally I would be really angry if I’ll stumble upon such code. See my earlier post about programs being written for humans to be understood.

If I wrote such a program, I would not call it "parse success" when I return Err, I would call it 'found' and be ambiguous. But It may be useful for ? to propagate the condition. I'm not so sure that such a program would be confusing.

Please, never write code like this. Using Result just to be able to use ? as a convenient shortcut is… I don’t know… It’s just wrong.

That is what everybody does with ?… that’s why it exists.

Yes, you tell the user which is success by what is Ok and what is Err. The semantics of Result is such that Err is the failure case.

Result is a type that represents either success ( Ok ) or failure ( Err ).

You have, by your own description, violated what Result represents.

2 Likes

No, ? was introduced for specific uses with Result and thus was imbued with a certain meaning. After that it was extended to Option and meaning of ? hasn’t changed much. Now we have (nightly) Try trait, which allows ? to be used with custom types. (note however names of the trait and its methods, again they create a certain expectation for how it should be used)

You could define your custom enum for your parsing library and impl Try for it, so you will be able to use ?, it will be a somewhat questionable approach, but it will be much-much-much less wrong than using Result and significantly less confusing.

There’s definitely some confusion here.

Lets say I write a function which returns Result<i32, u32>. I’m telling you that i32 is Ok and u32 is Err. Good?

This establishes a local convention. You could take what I gave you and rewrite it as a function which returns Result<u32, i32>. Thus there is no inherent ‘errorness’ associated with my Err(u32). It might be quick to propagate a u32 in my code, but now it isn’t in yours. In effect, you disagree with me about what is or is not an error, and that is reflected in your code, by the fact that you swapped the arguments in Result.

I think we’re all in agreement on that. All I’m saying is that, in my code, if I return Ok when an internal parser returns Err, then you certainly won’t care, because you can choose your own interpretation regardless.

They are relevant for semantic intent and how things are used.

Bug or not, it is a clear indication of what was thought as important and came first and what was then added later to fix the symmetry.

I'm not sure where this impl arises from... Product in std::iter - Rust doesn't have it.

That function would likely not read as ergonomically if you were using ? (which I think is a good idea...).

PS: What the Try<Ok = T, Error = E> trait is, is simply a tagless final interpreter of Result<T, E>.

Yes, because the idea of success/failure is local. Failure of one fn does not imply failure of the parent (thus why ? isn’t implicit).

My point is that there is a difference in what it means to return Result<Q, K> or Result<K, Q>.

A good example of Result<T, T> is https://doc.rust-lang.org/nightly/std/primitive.slice.html#method.binary_search. Here, “found” constitutes success and “not found” constitutes failure.

My point is that there is a difference in what it meand to return Result<Q, K> or Result<K, Q> .

But I can't know what you think the meaning should be, so it doesn't matter which one I return. This is key, because it means there isn't any meaningful difference. You will have to interpret it how you want to either way.

In other words, these two functions will both communicate the same thing:

fn ensure_match(&str, &str) -> Result<&str, i32>;
fn ensure_no_match(&str, &str) -> Result<i32, &str>;

It doesn't matter which one you have. In either case, you will be determining your own semantics for Result rather than using mine.

(Within those functions, maybe it does matter. But that's an implementation detail.)

I think it matters a great deal if you want to produce understandable APIs; the former seems much more understandable than the latter.

Yeah no... this is not a parenthetical; the whole try { ... } and ? business that is "internal implementation detail" bends heavily a certain way wrt. what APIs are ergonomic to implement.

1 Like

I don't buy it. If you want to ensure there's no match, I don't see how ensure_match is more understandable. That's not something that can be established out of context.

the whole try { ... } and ? business that is “internal implementation detail” bends heavily a certain way wrt. what APIs are ergonomic to implement.

And what APIs should be ergonomic to implement?

So I suspect you're trying to argue that one of these functions will better meet the requirements of some specific class of use-cases. If I'm writing those functions, I should take into account the use-case I'm trying to promote and choose the one that works best there. I believe this just defers the decision: the Result type doesn't encode any description of the use-case that should be promoted, and if it is convenient for some use, that doesn't mean it is improper in any other. It can be seen in the code as serendipity if it works well, or a good design if humans intentionally built it that way, but neither of these form a general rule to argue that a given implementation is wrong in the abstract.

1 Like

Generally, positive statements are considered "better". (It's super domain specific and opinionated though.)

1 Like

Technically, functions returning () do explicitly return () at the end of the function because ;'s turn the value of the preceding expression to (). You’ll note this because it’s often an error the leave off the ; on a expression which doesn’t have the () type.

1 Like