Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2018-02-01 01:58:13


On Wed, Jan 31, 2018 at 4:52 PM, Vinícius dos Santos Oliveira <
vini.ipsmaker_at_[hidden]> wrote:

> 2018-01-31 20:25 GMT-03:00 Emil Dotchevski <emildotchevski_at_[hidden]>:
>
>> Where is the compile time error? Example?
>>
>
> Check the first Outcome tutorials.
>
> This won't compile:
>
> std::cout << (convert(text) / 2) << std::endl;
>
> This will:
>
> OUTCOME_TRY(foo, convert(text));
> std::cout << (foo / 2) << std::endl;
>

Does this guarantee that your program is dealing with the error correctly?
Not at all, if the context within which you call convert is not
exception-safe, you'll get the same leak you'd get had you used exceptions.
The difference is that you'd also get a compile error if you forget to
check for errors, which a C++ programmer can't forget to do in this case.

This is similar to how you have to deal with object initialization if you
don't use exceptions. Compare:

class foo
{
  bool init_called_;
public:

  foo(): init_called_(false) { }

  result<void> init()
  {
    ....
    init_called_=true;
  }

  result<int> foo()
  {
    assert(init_called_);
    return compute_int();
  }

  result<void> bar( int x )
  {
    assert(init_called_);
    //use x
  }
};

....
foo a;
OUTCOME_TRY(a.init());
OUTCOME_TRY(x,a.foo());
OUTCOME_TRY(a.bar(x));

Now the C++ version:

class foo
{
  foo()
  {
    //initialize, throw on errors
  }

  int foo()
  {
    return compute_int();
  }

  void bar( int x )
  {
    //use x
  }
}

....
foo a;
a.bar(a.foo());

Note, I am not only making the point that the Outcome version is more prone
to errors and more verbose, I am saying that even if you make no logic
errors writing it, the result is a program that is _semantically_ identical
to the one a C++ programmer would write using exceptions, complete with the
bugs that could creep in if your code is not exception-safe.

Literally, there is no upside to that style of programming.

> You can't forget to handle the error case. You can look at a function and
> you'll immediately know if this function can fail or not.
>

In C++, we use noexcept to mark functions that can not fail.

> You can't draw conclusions only by looking at anectodal evidence like
>> this. If you want to demonstrate that Rust-style error handling is more
>> robust, you have to have a control, the same real-world large scale project
>> written/designed for using C++ exception handling. Neither of us has a
>> control.
>>
>
> At least you dropped the "axiomatic belief". But I haven't relied on
> anecdotal evidence. I specifically told you to show me /any/ code on Rust
> that is as complex as the examples I gave.
>

Yes, I understand that you like Rust and dislike C++. :)

I am well aware of the difficulties in writing C++ code. Like I said, with
some exceptions, there is usually a good reason for that.

> C++ allows complexity to just snowball and it is not a opt-in feature.
> It's always there. Therefore, you always maintain useless state in your
> head when analysing C++ code. Go ask C programmers what they think about
> C++ exceptions.
>

You're making my point, that C++ exception handling is not an opt-in
feature, it is inseparable part of the language, central to its object
encapsulation model, etc. etc. I know what C programmers think about C++
and about exceptions in particular. Do note that they don't propose C
libraries to be added to Boost or C++, they don't want to touch either.

By the way, I have a lot of respect for C and for people who choose it over
C++. I've also learned the value of using C-style interfaces between
modules in a large scale C++ projects.

> So you have to be able to make your point in the abstract, which is very
>> difficult. It is especially difficult because using either approach, a high
>> quality development team can produce a robust program. But just because
>> Google managed to build a phone using Java, it doesn't mean it was a good
>> choice.
>>
>
> You still ignore the fact that I'm talking about a qualitative (not
> quantitative) property: more errors go to compile time errors. I have been
> focusing on such aspect since the beginning of the discussion.
>

I demonstrated why this is false, but there is no such thing as free lunch.
Everything has cost. It is important to keep this in mind when criticising
C and C++.

> It's fact that you can't ignore the error because the code will fail to
> compile. Error cases are explicit.
>
> Yes, using exception handling you can make mistakes that would be less
>> likely to make otherwise. Also, using C++ you can make a whole lot of
>> mistakes you can't make in Java, that's one reason why some people use Java
>> and not C++, but this doesn't make them good C++ programmers -- nor it
>> means that there is something wrong with C++.
>>
>
> Agreed.
>
> There are very many examples of things being very difficult to express in
>> C++ while being trivial in another language. There are usually good reasons
>> for this, and there are many examples to the contrary. The ability to use
>> RAII and exceptions to enforce postconditions is one such advantage.
>>
>
> Not sure what kind of postcondition you're talking about this time.
>

What postcondition, that depends on the design, but the effect is that
you're making it impossible for control to reach contexts for which it
would be a logic error to execute in case a previous operation failed.

> If it is class invariants, you can have them without exceptions.
>

You can have them, but you can't know if they're been established because
in case of a failure, control may still reach code that requires them to be
in place.

> And just one note about RAII: Rust does have RAII. No GC. A lot about the
> language was inspired by C++ knowledge.
>

Yes, I get that you love Rust. :)

> I'll point out again that in C++ it is impossible to report an error from
>> a constructor except by throwing, and this is a good thing. I assume you
>> disagree -- so what do you propose instead? It can't be OUTCOME_TRY, can it?
>>
>
> I'm not proposing you to code differently.
>

My question still stands though. If you don't use exceptions, in C++, how
do you protect users from calling member functions on objects that have not
been initialized?

Emil


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk