1. 18
  1. 4

    The naming scheme for “void” always confused me when comparing:

    • C/C++ inspired languages and their ilk
    • more functional-inspired* languages.

    In C (etc.), void means “doesn’t return anything”, which is different from “doesn’t return at all”.

    In many functional languages, void is the bottom type, meaning a function returning it “doesn’t return at all”. Their functions usually return the “unit type” for the “doesn’t return anything” case.

    Where did this divide come from?

    * funnily enough, python implicitly returns None from a function that reaches its end without a return. And within python, NoneType is the only builtin that could be considered a “unit type”! I’m sure there are other similar examples.

    1. 2

      Interesting. I hadn’t realised that there’s a point to [[noreturn]] int fn() (the thing that we don’t return is an int), but I can see that you may well want to allow a function to slot into a code path that will set up a call frame expecting a specific return type. I still find it a bit confusing that [[noreturn]] functions are allowed to return via exception throwing. You can also write [[noreturn]] int fn() noexcept, which isn’t allowed to return via any mechanism and which frees the compiler to destroy any non-escaped caller state at the call site.

      The lack of participation in the type system is a bit sad, because it means that you can’t guarantee that a function pointer is noreturn. C++17 changed this for noexcept and that leads to some interesting interaction with std::function, which doesn’t have overloads for noexcept functions because it must be able to handle the case where it is uninitialised. You could imagine a non-standard version that required the return type to be default constructable and was initialised to point to a function that returned a default-initialised value, which would not have this constraint.

      1. 1

        I still find it a bit confusing that [[noreturn]] functions are allowed to return via exception throwing.

        I never thought of throwing an exception as “returning”. Do you think of a function longjmp’ing as also “returning”?

        […] std::function, which doesn’t have overloads for noexcept functions.

        FYI, C++23 added std::move_only_function which does have specializations for noexcept.

        1. 1

          My mental model for a function not returning is that no code on the stack above that invocation is ever reachable. In particular, the compiler is free to trash anything on the stack in setting up the call frame and is free to not bother emitting any code to handle anything after the call instruction. Without noexcept, most of this is not true for a C++ [[noreturn]] function. I’m actually curious what the standard says if you longjmp out of a [[noreturn]] function and whether noexcept makes a difference.

      2. 1

        As of a few versions, you can just use the return type “noreturn” in D. https://twitter.com/walterbright/status/1364135799369789441

        1. 1

          Returning void means you return no value. [[noreturn]] means you don’t return at all. ie, put a debugger breakpoint right before the function’s } and it will never get triggered.