Hacker News new | past | comments | ask | show | jobs | submit login

Few other languages have move semantics. Rust does, but has a quite different value model overall.



Our equivalent to lvalue/rvalue is “place expression” and “value expression” https://doc.rust-lang.org/reference/expressions.html#place-e...


I like that so much more!


Also, Rust does not have copy or move constructors/operators; every copy or move is a pure memcpy() (there's Clone for when you need it, but clone is always an explicit call). This simplifies the model a lot.


C++'s move constructor can throw exceptions. An example would be moving a linked list, and the allocation of dummy tail node can fail. In my opinion, the design choice to support recoverable allocation failure in C++ reasons for most other designs.


You can make allocation failure recoverable in different ways. For example, Rust’s (yet to be stabilized) allocator API returns a Result: https://doc.rust-lang.org/stable/std/alloc/trait.Alloc.html#...

It is true that choosing to make this recoverable is a big part of it; in Rust we decided to make standard library data structures treat allocation as unrecoverable, and this does simplify the API of using them significantly. However, I would argue that the complexity in C++ comes more from using exceptions for this purpose than from that choice. Rust’s panics being used for unrecoverable errors only helps simplify the overall model. In other words, the ease of use goes unrecoverable > recoverable through result > recoverable through exceptions, and the model complexity comes from the choice of using exceptions over return values. We do have a plan for introducing a way to make the data structures recoverable, but it’s waiting on several things.

This is, of course, only my opinion.


Exception is also a result of supporting recoverable allocation/construction failures, IMO. When you need to support a failable Vec::new, what will be the type of its Error? There are two choices in this case:

a) wrap underlying errors into something like VecNewError, which is tedious due to the prevalence of errors if every construction/allocation can fail, kindly similar to the CheckedException dilemma

b) type erasing it by Box it or dyn Error, and use ? operator to transparently pass errors to upper level, in which case the user need to downcast with match to extract information needed, and exception is essentially the syntax sugar for this case because exception acts like a ? operator after all failable function calls

a) is usually used in a language where errors are rare, and b) is preferred when the errors are common.


Vec::new doesn't allocate.

Vec::try_reserve, on the other hand, simply uses a common `CollectionAllocError`, which is not really (a) or (b).


Why would moving a list have to allocate a new tail node? A lot of the value in moves is passing data around without having to copy/duplicate/allocate.

The most useful aspect of having customisable move constructors like in C++ seems to be things like updating pointers in self-referential types, which hopefully can be written to never throw.


It's unfortunate but some C++ standard library implementations do require allocation in some moves (notably, IIRC, Microsoft's std::list). So we're stuck with it.


Huh, that is unfortunate!


Rust moves don't allocate, and yet can still support recoverable allocation failure.

The reason C++'s model is so much more complicated is because of backwards compatibility and nondestructive moves, not as a mechanism to support recoverable allocation failure.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: