ned14 / outcome

Provides very lightweight outcome<T> and result<T> (non-Boost edition)
https://ned14.github.io/outcome
Other
704 stars 62 forks source link

Experimental status_result should use errored_status_code for defaulted E type in Outcome v2.2 #227

Closed hazelnusse closed 4 years ago

hazelnusse commented 4 years ago

This probably exists and I simply can't find it. status-code permits multiple success enums to be defined, as shown here: https://github.com/ned14/status-code/blob/a7dd7e3c0e967c9aeb881b7582a56ea2121d8712/test/main.cpp#L52

Is there an example of status_result or status_outcome being used with such a status_code? In particular, I'm curious how the status_result<T> gets constructed with the enum value indicating it is one form of success vs another, and how the status_result/status_outcome library distinguishes between success/failure in this case -- what would prevent someone from returning a value along with an error enum that would make things inconsistent?

Forgive me if this is sitting right in front of me in the documentation, I've poked around and can't seem to find anything.

ned14 commented 4 years ago

Is there an example of status_result or status_outcome being used with such a status_code? In particular, I'm curious how the status_result gets constructed with the enum value indicating it is one form of success vs another, and how the status_result/status_outcome library distinguishes between success/failure in this case -- what would prevent someone from returning a value along with an error enum that would make things inconsistent?

So the answer is really obvious, though it won't be to your liking: you ought to not set successful or warning status codes as the .error() in an Outcome result, because that's confusing. In my own personal code, I enforce this by:

template<class T> using result = outcome_e::status_result<T, errored_status_code<erased<myerrorinfo>>;

Should Outcome's defaulted template parameter for status_code not do the same? Yes I think it should, so I've renamed this issue to pend the change for Outcome v2.2. Thanks for raising the point.

hazelnusse commented 4 years ago

Ok, that is helpful, thank you. That prevents clients from doing something non-sensical (returning a success error code as an error).

What I'm asking is slightly different though: Can you show me an example of how the return of valid result in the success1 case varies from the success2 case? Normally I've had a single success enum and in those cases simply return the T which gets wrapped automatically by the status_result, but I don't understand how the code would look different in case of returning T, success1 vs T, success2?

ned14 commented 4 years ago

I should stress that I don't think that you should use successful status codes with result<T, E>. It is anti-social. But if you really did want to, simply return failure(errc::success1) or failure(errc::success2) to set the error return channel to the code, just the same as you would for a failure code.

If I were writing code which could return a failure, a warning, or success, I'd use a different API design:

my_code myfunction(Foo &out, ...);

Foo obj;
if(auto r = myfunction(obj); r.success())
{
  // Was there a warning?
  if(r.value() = my_code::success_but_warning) ...
}
else
{
  // Throw the failure as an exception
  r.throw_exception();
}
ned14 commented 4 years ago

Fixed.

hazelnusse commented 4 years ago

Thanks @ned14 I think this makes sense now. Just to be clear, as you wrote it above, out would be set in myfunction assuming it was able to? And when r.value() == my_code::success_but_warning, you'd do something like logging or maybe attach some extra data to the status code via a mixin?

ned14 commented 4 years ago

Obviously you can swap the Foo &out for my_code &out as would be idiomatic in code such as ASIO. However, then you run into the problem that people tend to reuse my_code instances, and if you don't reset them before each function call you get misoperation. So I'd prefer the form I suggested.

For the use case for warnings, my principle use case was NT kernel return codes and COM return codes, where it might come back with "success this time, but next call or two I might run out" or "I succeeded, but I processed fewer items than supplied". That sort of thing.

Me personally, generally speaking failures are what you attach extra payload onto. Warnings tend to be success with additional information, no extra payload needed as it's all already in the warning code.

But equally, you may have other use cases. So, really, "it depends".