C++ Logo

std-proposals

Advanced search

Re: Middle ground between "return" and exceptions?

From: Dmitry Dmitry <dimanne_at_[hidden]>
Date: Mon, 12 Oct 2020 22:29:57 +0100
Hi all,

I understand it is slightly off-topic, but *I really wanted to thank Barry*
for his elegant and beautiful idea, I have successfully implemented it and
the code below feels just other-worldly:
I can now use it in this way:
TCoResult<int, std::string> CreateSocket() {
   static int Counter = 1;
   if(++Counter % 2 == 0)
      co_return "Failed to CreateSocket: SysErr: EINVAL Invalid argument";
   co_return 42;
}
TCoResult<int, std::string> OpenSocket() {
   const int Socket = 2 + *co_await CreateSocket().OrNestAndReturn()*;
   co_return Socket * 2;
}
TCoResult<int, std::string> ConnectSocket() {
   co_return *co_await OpenSocket().OrNestAndReturn()*;
}
TCoResult<std::string, std::string> ReadSettings() {
   *co_await **ConnectSocket().OrNestAndReturn()*;
   co_return OkRes("Here is our settings");
}
int main(int, char *[]) {
   std::cout << ReadSettings() << std::endl << std::endl;
   std::cout << "==== Second attempt ====" << std::endl;
   std::cout << ReadSettings() << std::endl << std::endl;
   return 0;




* // Expected output: // Err(Failed to ReadSettings @ Line:20: Failed
to ConnectSocket @ Line:17: Failed to OpenSocket @ Line:13: Failed to
CreateSocket: SysErr: EINVAL Invalid argument) // ==== Second attempt
==== // Ok(Here is our settings)*}

In case you are interested here is the godbolt link with complete
implementation to play around:
https://godbolt.org/z/8hxhttps://godbolt.org/z/8hxKTWKTW
<https://godbolt.org/z/8hxKTW>
and here is repo
<https://github.com/DimanNe/result/blob/master/examples/main.cpp>.

PS OrNestAndReturn() uses source_location to generate messages with line
numbers and function names.

On Mon, Sep 14, 2020 at 8:04 PM Barry Revzin <barry.revzin_at_[hidden]> wrote:

>
>
> On Mon, Sep 14, 2020 at 1:44 PM Dmitry Dmitry via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> TL;DR: What do you think about creating a new flavour of control-flow
>> statements (in particular "return") that could return from outer/parent
>> scope?
>>
>> In the thread "*Initialisers in ternary operators*" we had this example:
>> std::optional<std::string> FindUsersCity(bool non_default) {
>> std::optional<ContactsServer> contacts =
>> GetOrOpenContactsServerConnection();
>> if(!contacts)
>> return std::nullopt;
>> std::optional<UserId> uid = contacts->GetUserId();
>> if(!uid)
>> return std::nullopt;
>> std::optional<GeoServer> geo = GetOrOpenGeoServerConnection();
>> if(!geo)
>> return std::nullopt;
>> std::optional<Location> uloc = geo->GetLocation(*uid);
>> if(!uloc)
>> return std::nullopt;
>> return uloc->GetCityName();
>> }
>>
>> It seems, that there are many situations with the following pattern:
>>
>> 1. there is a type
>> 2. that defines some invalid state (in case of iterator the state is
>> "being equal to end()", in case of Result/Outcome/Expected the state is
>> "representing an error", in case of optional it is "nullopt", and so on...)
>> 3. attempts of using which in the invalid state are supposed to
>> "return" (from the scope where it is used). We can take a step back and
>> consider other control-flow statements.
>>
>>
>> Without going into implementation details, what do you think about the
>> idea (on a conceptual level) of being able to describe this pattern
>> somewhere somehow?
>>
>> If conceptually it is fine, then what do you think about implementing the
>> idea by creating another flavour of return statements that can return not
>> only from its own scope, but also from outer/parent scope?
>> If you wish, this new flavour can be viewed as a middle ground between
>> (1) regular return's (which often requires boilerplate for handling the
>> pattern above) and (2) exceptions (which are too non-local and implicit and
>> can "jump" at an arbitrary place).
>>
>> The code above would look like this:
>> std::optional<std::string> FindUsersCity() {
>> std::optional<ContactsServer> contacts =
>> GetOrOpenContactsServerConnection();
>> std::optional<UserId> uid = contacts.
>> *GetOrReturnNullOpt()*->GetUserId();
>> std::optional<GeoServer> geo =
>> GetOrOpenGeoServerConnection();
>> std::optional<Location> uloc = geo.*GetOrReturnNullOpt()*
>> ->GetLocation(*uid);
>> return uloc.*GetOrReturnNullOpt()*->GetCityName();
>> }
>> where *GetOrReturnNullOpt()* is a method that (1) either returns a value
>> if std::optional is not empty, (2) or returns from FindUsersCity() if
>> std::optional is empty.
>>
>>
>> I think it could solve
>>
>> - Initial problem mentioned in "Initialisers in ternary operators":
>> we could have something like this: return Cont.find(42).GetOrReturn(0)
>> .
>> - The problem of using optional without boilerplate (see above).
>> - The problem of Error-handling via Result<> without boilerplate,
>> which has been a constant source of pain and complaints and holy wars
>> between exceptions and return-values for error handling.
>> - It also eliminates one more reason to exist for macros (that is how
>> people are solving it right now: 1
>> <https://stackoverflow.com/questions/51608396/how-to-exit-current-function-if-error-on-current-statement-in-c>,
>> 2
>> <https://stackoverflow.com/questions/6932401/elegant-error-checking/6933170>
>> ).
>>
>>
>> Any thoughts?
>>
>>
>> --
>> Dmitry
>>
>
> As I said in https://lists.isocpp.org/std-proposals/2020/09/1811.php,
> this already exists in C++20 in the form of coroutines... you just need to
> opt in support for optional<T> in the only sensible way and then you can
> write:
>
> std::optional<std::string> FindUsersCity(bool non_default) {
> ConstactsSeriver cs = co_await GetOrOpenContatsServerConnection();
> UserId uid = co_await cs.GetUserId();
> GeoServer gs = co_await GetOrOpenGeoServerConnection();
> Location uloc = co_await gw.GetLocationId(uid);
> return uloc.GetCityName();
> }
>
> co_await here is effectively syntax sugar for and_then() (in the same way
> in Haskell do-notation is syntax sugar for bind). It's just that the
> continuation gets written as the next statement in the function rather than
> the body of a lambda that gets passed as an argument.
>
> Barry
>


-- 
Dmitry
*Sent from gmail*

Received on 2020-10-12 16:30:28