1. 2

    Scripting languages are being removed, yet Python will apparently be updated to 3 for LLDB for XCode. Can we assume that LLDB is embedding Python, thereby not leading to a system-wide Python 3 being available by default in Catalina?

    If so, that’s a bit of a shame. While system Python installations are best avoided, they can be useful for bootstrapping isolated versions with invocations like python3 -m venv.

    Even so, I understand Apple wanting to simplify the basic tools in their OS from the “every popular Unix program you’re used to, preinstalled” approach from the early Noughties.

    1. 5

      Maybe Python 3 will not be present OOtB but it will be installed when you install xcode-tools.

    1. 17

      However, it also defines operator* and operator->. These methods also provide access to the underlying T, however they do not check if the optional actually contains a value or not.

      Wat?

      1. 15

        Using operator* or operator-> on an optional that doesn’t contain a value is undefined behavior, see: spec, cppreference

        I’ve been about to migrate a bunch of APIs from std::unique_ptr<T> to std::optional<T> and now I’m really not feeling as great about that plan.

        1. 19

          On the face of it this seems like an insanely bad API design decision by the committee.

          1. 5

            What should it do? Throwing exceptions would make the API useless to many large consumers. Default-constructing the value type would probably be worse. The C++ type system just isn’t very good.

            1. 25

              What should it do?

              not offering those operators at all and instead forcing the usage of map, flatmap or get_or_default like most languages do.

              We opensourced a library that implements a more functional API here

              1. 2

                What should it do?

                not offering those operators at all and instead forcing the usage of map, flatmap or get_or_default like most languages do.

                That’s unacceptable when you know the optional has a value and performance is critical. Nothing is stopping you from implementing “get_or_default” on top of the operations provided, but you cannot implement operator* as it currently is on top of “get_or_default”

                1. 4

                  That’s unacceptable when you know the optional has a value and performance is critical.

                  C++ is a zero cost abstraction language and lambdas get inlined. Unless the optional is implemented via a vtable, you should not see any overhead.

                  1. you might be right today but what about tomorrow? - will your assumption still be true if someone else will modify some (distant) part of the codebase?

                  2. If you know that an optional will always contain a value, why is it in an optional in the first place?

                  Nothing is stopping you from implementing “get_or_default” on top of the operations provided, but you cannot implement operator* as it currently is on top of “get_or_default”

                  Well the argument is operator* you should not exist as it eventually leads to unsafe code and bugs. You may always know when you can get away of using operator* without prior checks and may not need such construct but most people get it wrong at least once.

                  If you work in a team you will realize those unsafe usages become a liability as they are prone to break due to distant changes. As a result this is not very social: this practice pushes the responsibility of debugging or verifying your assumptions to everyone who is contributing to the codebase. Every Time someone works on a feature and coincidentally breaks the unsafe code will also need to fix it up; it becomes his responsibility.

                  Lastly, just for the sake of argument: In case your optional implementation does not include a footgun, this is how you could build one:

                  T const& optional<T>::operator*()const {
                     return this->get_or_default(*(T*)nullptr);  // that's fine it's just undefined behaviour 
                  }
                  
                  1. 2

                    You shouldn’t be forced to check if the option is filled each time you access it. One check is enough, then you can assert that the option is filled in all following code paths. If you don’t want to do that, force your team to always use “get_or_default” but don’t force the world to work like your team, not everyone has your performance constraints.

                    1. 1

                      You shouldn’t be forced to check if the option is filled each time you access it.

                      You only test once. If you are worried that running map or get_or_default test for the content multiple times, don’t worry, the compiler will optimize it away: https://godbolt.org/z/7RGUCf

                      1. 1

                        That’s not true in all cases.

                  2. 2

                    If you know a value exists and performance is critical …why are you using std::optional in the first place?

                    By this logic, you might as well demand that std::map should have implemented operator[] not to take const Key& key but instead an already hashed bucket identifier, and if there’s nothing in that bucket, or not what you were expecting, oh well, have some undefined behaviour?

                    It’s silly. If you don’t want the overhead of a particular container, don’t use that container.

                    Insisting that std::optional container has to have a hole in it … either you want an Option or you want a raw pointer. “I want an Option but it has to behave exactly like a bare pointer” is an incoherent position for an API trying to satisfy everyone and ending with something useful to no one. “It’s safe as long as you always 100% without fail manually check it before dereferencing, otherwise undefined behaviour” adds nothing that wasn’t already available.

                    1. 2

                      If you know a value exists and performance is critical …why are you using std::optional in the first place?

                      What if you already checked it once? Why should you be forced to check each time you access the contained value?

                      Insisting that std::optional container has to have a hole in it … either you want an Option or you want a raw pointer.

                      It’s not a raw pointer, it’s a boolean paired with a value. There’s no necessary indirection.

                      1. 2

                        What if you already checked it once? Why should you be forced to check each time you access the contained value.

                        Then extract the value the first time? Why would you design an entire API around “people might unnecessarily check for whether some value is present multiple times”? Leave Nones at the edges of your program. Once you’ve determined that there is Some(x), everything else should be dealing with x, not the Option it started in.

                        The question, if anything, illustrates exactly why these are bad operators to have in the API.

                        Without them, Options are cumbersome enough that you very quickly realize slinging them around in the core of your program is silly; you extract the present value, substitute a default, or deal with an unsubstitutable None at the edge, and design everything else in terms of the value.

                        With these operators apparently a nonzero subset of people think “I should just leave the value in the Option and touch its raw memory because “I know” I checked for presence once upstack”. Which works until someone refactors something and suddenly there’s a codepath that pulls the trigger on your footgun.

                        Good API design guides the user towards good usage. This clearly doesn’t.

                        It’s not a raw pointer, it’s a boolean paired with a value. There’s no necessary indirection.

                        It’s a raw pointer (T*) you’re getting back in the case of operator* and operator->, the operators this entire thread of posts is discussing. If you’re going to blindly call those you could equivalently have just slung around raw pointers wherever you acquired the option to begin with.

                2. 9

                  I mean, the semantics of Option in the abstract just aren’t safely compatible with an operation that blindly assumes Some(x) is present and directly grabs x’s memory, so the choices are either:

                  1. use exceptions to avoid unsafe accesses at runtime, which as you say, not every codebase will appreciate

                  Or

                  1. just don’t offer those operators on Option. They fundamentally compromise the entire point of the type. If you want to make blind assumptions about memory being initialized, use a raw pointer; Option should enforce its safety benefits.
                  1. 3

                    What should it do? Throwing exceptions would make the API useless to many large consumers.

                    And undefined behavior makes it usable?

            1. 2

              I see an interesting contradiction by those arguing that such failures are simply C++ being misused: they often argue that Modern C++’s biggest advantage over plain C is the myriad features that make development safer, like std::string and smart pointers.

              Couldn’t their arguments against Rust also be levelled against using C++ over C? After all, who needs the safety afforded by destructors and std::vector if we can simply program in C carefully with popular collection and string libraries?

              It seems that the most compelling point in favour of using Modern C++ over C is also the best argument for using Rust over Modern C++.

              That’s not even taking into account the Modern C++ features mentioned like reference capturing lambdas and classes like std::span that add even more unsafe foot guns over good ol’ K&R C.

              1. 4

                Nice work, always nice to see more BEAM-based languages. A quick question, apologies if I missed it in the documentation already: how does the static typing handle messages from remote processes that are potentially on future versions, i.e. messages that aren’t type-compatible with the receiver process?

                I ask because Akka in the JVM world ran into similar problems and then just fell back to java.lang.Object and instanceof checks for actor messages, and Erlang naturally doesn’t encounter this problem due to dynamic typing.

                Rich Hickey makes interesting points in his talks about type systems, pointing out that incompatibilities between type changes shouldn’t really occur for additive changes, but that would seem to require structural typing to support and be compatible across multiple versions of the same codebase, from what I can see.

                Thanks.

                1. 7

                  How we should type message passing is the subject of ongoing research and as a result there is no built in constructs for the BEAM’s concurrency primitives in the language. If you wish to use them you’d use the FFI and write this code in Erlang, so it would be dynamically typed.

                  As for the talk from Rich Hickey, my preference is to have breaking changes and to have tooling that makes resolving these problems straightforward rather than always expanding the existing API. This will definitely be the route that Gleam takes with message passing as we will likely go through before the first stable release.