5 ms·
> Both C++ and rust encourage exclusive ownership. And that's a good thing. It's not. It makes lots of important things unnecessarily hard and often leads to u
by rbehrends 10y ago
> Both C++ and rust encourage exclusive ownership. And that's a good thing.
It's not. It makes lots of important things unnecessarily hard and often leads to unnecessary copying [1]. It makes it hard to even do something like OCaml's List.filter and Array.filter properly. It gets in the way of doing functional data structures (ex: binary decision diagrams). Lots of common design patterns also require shared ownership.
> HOF tie resources to lexical scope; RAII ties them to object lifetimes, recursively. In turn lifetimes may be bound to a lexical scope, but often is not the case.
No, higher order functions don't per se tie resource management to lexical scope (though that's the easiest application). You can build an entire transactional model on top of higher order functions.
Second, tying resource management to object lifetime is dangerous, as object lifetime can exceed the intended life of a resource (ex: closures, storing debugging information on the heap).
Third, RAII is in practice little more powerful than lexical scoping. RAII works poorly for global variables (problems with initialization/destruction order) and thus is in practice limited to automatic and heap storage. Using heap storage leads to the aforementioned problems, where object lifetime can become unpredictable.
[1] https://news.ycombinator.com/item?id=8704318 https://news.ycombinator.com/item?id=8704318
- gpderetta 10y ago>It makes lots of important things unnecessarily hard and often leads to unnecessary copying [1] If you read the article referenced by that thread you'll see that a major issue with string copying is due to having a lot of bad interfaces taking raw C pointers so code at both side of the ibterface need to make copies exactly because the raw C pointer doesn't guarantee exclusive ownership. The remaining issues are due to a badly optimized string buider in Chrome and failure of pre-reserving vector memory which lead to many copies on resize. This last issue is fixed with move semantics in c++11. >It makes it hard to even do something like OCaml's List.filter and Array.filter Std::remove_if works genrically on any range. Boost (and the range TR) provides iterator views when you need lazy evaluation. > You can build an entire transactional model on top of higher order functions. I'm sure you can, in the end you can implement anything manually. With RAII propagation of lifetimes is done automatically by the compiler. >Using heap storage leads to the aforementioned problems, where object lifetime can become unpredictable. Only if you use shared ownership. Otherwise is completely predictable.
- rbehrends 10y ago> If you read the article referenced by that thread you'll see that a major issue with string copying is due to having a lot of bad interfaces taking raw C pointers so code at both side of the ibterface need to make copies exactly because the raw C pointer doesn't guarantee exclusive ownership. The remaining issues are due to a badly optimized string buider in Chrome and failure of pre-reserving vector memory which lead to many copies on resize. This last issue is fixed with move semantics in c++11. The point is that something like: List.filter (fun s -> String.length s > 0) list simply cannot be done efficiently, because you require either copying or shared ownership for the strings. This also occurs naturally in a number of other situations, such as storing strings in objects. > Std::remove_if works genrically on any range. Boost (and the range TR) provides iterator views when you need lazy evaluation. std::remove_if is destructive. Iterators are not the same thing as a functional filter operation. > I'm sure you can, in the end you can implement anything manually. The point here is that a transactional system is more powerful than RAII. > Only if you use shared ownership. Otherwise is completely predictable. If you don't use shared ownership, then you're basically limited to lexical scoping.
- gpderetta 10y ago> The point here is that a transactional system is more powerful than RAII my point was that RAII trivially maps to transactions (destructors do rollback and commit is explicit). Transaction RAII object can be composed to make more complex transactions. > If you don't use shared ownership, then you're basically limited to lexical scoping. why would you say that? A common use case is having objects manually removed from collections triggering cleanup actions, like closing sockets, automatically de-registering from event notifications, sending shutdown events, rolling back transactions. That has nothing to do with lexical scoping.
- rbehrends 10y ago> my point was that RAII trivially maps to transactions (destructors do rollback and commit is explicit). Transaction RAII object can be composed to make more complex transactions. RAII is limited in that it's tied to object lifetime, whereas more general transactional semantics can be linked to more general semantic conditions. Also, RAII does not have a good way to distinguish between commits and aborts. > why would you say that? A common use case is having objects manually removed from collections triggering cleanup actions, like closing sockets, automatically de-registering from event notifications, sending shutdown events, rolling back transactions. That has nothing to do with lexical scoping. If you use an explicit action to trigger destruction, then this doesn't have to be a deletion. In fact, having it tied to object destruction is unnecessarily limiting. The general rationale for having RAII is that it happens automagically; if explicit disposal is needed, then much of that rationale goes away. In general, it seems to be me that you don't have actual experience with resource management outside of C++, so you're mostly speculating about what it's like and try to force your thinking about it into a C++-like model.
- steveklabnik 10y ago> often leads to unnecessary copying [1]. Rust is very different from C++ here. You wouldn't get those copies unless you directly called .clone(), so you'd know you were doing a deep copy.
- rbehrends 10y agoThat's not the point that I'm getting at. Exclusive ownership invariably mandates copying for certain use cases. Rust allows you to obviate this in a few more cases through borrowing, but as a general rule, exclusive ownership and having multiple references to the same object do not mesh. You need to get rid of either one or the other. That Rust forces you to be explicit about copying in those cases does not make the underlying problem go away. This is especially noticeable and constraining when you come from a functional programming background and not C++ or when you're doing stuff that's more complicated than shuffling bytes (I've mentioned binary decision diagrams as an example). The bigger point is: as it is extremely rare for me to write one of the few niche applications that are actively GC-hostile (such as web browsers, AAA video games, or OS kernels), I don't see the point of jumping through all the extra hoops that avoiding GC brings with it.
- steveklabnik 10y ago> Exclusive ownership invariably mandates copying for certain use cases. Hm, why would you not use multiple ownership then, instead?
- sqeaky 10y agoI think that is his point, he thinks Rust and C++ cannot do this. But is the default in C++ and well done in Rust.
- rbehrends 10y agoNo, my point is not that it cannot be done (both Rust and C++ are Turing-complete, so "cannot be done" does not make sense for anything that's a computable property), but that it comes with a cost. See my other response for the details.
- charlieflowers 10y ago> Third, RAII is in practice little more powerful than lexical scoping. I'm beginning to think that we're all going about things wrong :) . We like and rely on things like RAII, macros, etc. But these are nothing more than specific compiler features. If we were to take control of generating our own code, we could have RAII, macros, and whatever else we dreamed up, easily. And the generated code could be in some readable, debuggable language, so much more visible and obvious than the results of a macro expansion. For so long we've relied on an amazing black box called a "compiler", when perhaps we should take on the responsibility ("power") of implementing a compiler ourselves. (We could still, of course, generate some intermediate mainstream language, and then those amazing black boxes could take that as input and apply all their optimizations. Seems to yield clearer code, less magic, more power, and more portability.