Boost logo

Boost :

Subject: [boost] [outcome] narrow-contract, wide-contract, and value_if
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-30 22:36:24

Hi Everyone,
Some comments about `outcome`'s observers with narrow and wide contracts.

First, the wide-contract functions.

We have a situation when someone assumes that `o` currently stores an
error_code, and with this assumption in mind calls `o.error()`, but somehow
his assumption is incorrect. What do we want to do?

1. Try to report programmer's bug at run-time?
2. Since in this case it is a bug anyway and are screwed, at least let's
make such behavior useful for other situations where `error()` is called
conciously on `o` without an error code?

The first scenario attempts at identifying a bug, bringing it to attention
of developers (maybe in form of a log), and minimizing its consequences by
perhaps skipping the execution of some part of the program in hope that the
bug will no show symptoms again.

The second scenario in a way ignores the bug, and lets the program
continue, as though it was correct, and from now on the program is doing
something else than what the programmer intended. But the semantics of the
function are useful in itself. Users who check `o.has_error()` before
calling it can use it as if `error()` had a single purpose and a narrow
contract; and users who know the additional semantics can use it for other
useful things, like saving themselves of writing some boilerplate code.

It is my impression that some of these observer functions in `outcome`
follow this second scenario: just do something useful for people who use it
unchecked. Saving boilerplate is useful, but let's state it clear: these
functions offer no protection against inadvertent mistakes: no run-time or
static protection.

If I call `o.error()` and it throws when `o.is_empty()`, it is a form of
protection. If `o.error()` returns `E{}` when `o.has_value()`, this is no
protection, it is convinience function that can be used as a
narrow-contract observer.

Maybe the interface of `outcome` should make this distinction and offer two
sets: one for observers with run-time protection, and the other for nice
convenience functions?

Second, narrow-contract functions.

Narrow functions acheive the following goals:
1. Simple design: function only does one thing, it does not have any if
2. The interface of the library makes a clear distinction of what is valid
and what is an invalid usage of the library. It helps in code reviews. For
instance, you are looking for a bug in a big code, you find the following

auto e = o.error();

and it turns out that `e` contains a value. Did you just find a bug, or was
it the author's intention to get an `E{}` at this point? You do not know
with wide contracts. With narrow contracts you know: it is a bug.

3. Narrow contracts assist instrumentation in test builds and runs.

If I have function `just_value` with a narrow contract, I leave myself the
freedom: I can do whatever I want when has_value() is false. What can I do
with this freedom:
A. In test build, I can check the precondition at run-time and launch the
B. I can (run-time) check the precondition, and std::termiante() or throw
an exception.
C. I can ignore the potential precondition violation, check nothing and aim
at some performance gain.
D. I can put some code that assists the static analyser (like
E. I can put some instrumentation code that assists an UB sanitizer (like
F. I can put hints for optimizer (like __builtin_unreachable(), again)
G. Finally, I can put a macro `PRECONDITION(has_value())` which can do any
of the above based on the build configuration.

Given that, the proposed function `value_if` (which returns a pointer to
value) will not satisfy my needs. It has a wide contract, so I cannot put
macro `PRECONDITION()` inside. It rturns a pointer. The pointer's
operator-> inded does have a narrow contract, but it gives me no room where
I can put macro `PRECONDITION()`. So I get some narrow contract, but
without the benefits I am looking for.

So that is my view of the observer functions. Now my proposal is to provide
a pair of observers for each value, error_code and exception -- narrow and

`value()` and `just_value()`, `error()` and `just_error()`, `exception()`
and `just_exception()`

Or maybe, even three for narrow-contract, wide-contract with run-time
protection, and for convenience function:

`error()` -- wide-contract, runtime-checked precondition
`just_error()` -- narrow contract
`as_error()` -- convenience function, e.g. returns E{}, on value,
monad::has_exception on exception, etc..


Boost list run by bdawes at, gregod at, cpdaniel at, john at