3 ms·
>If defining value semantics was so simple you wouldn't have had to bring up Pin pointers (which I assume are not just pointers, but something special that need
by trealira 3y ago
>If defining value semantics was so simple you wouldn't have had to bring up Pin pointers (which I assume are not just pointers, but something special that needs to be kept in mind) [...]
Pin<P> is basically a wrapper for any pointer type P, whether it's Box<T>, &mut T, &T, etc. Its sole purpose is to keep the object that's pointed at from being moved, byte-copied, or byte-swapped. For self-referential structs, that's all you can do; there's no equivalent to move constructors. See this link at the bottom [1].
> This is incorrect, somewhat. [...]
You're right, but I really just meant that you have to keep track of more. If you use a moved-from unique_ptr, for example, you'll dereference a null pointer. Sometimes you do want to use a moved-from object, though, so it's not like this can be disallowed. It's something you have to keep track of. I just think this is more complex than Rust's unconditional rule that moves are bytewise copies, and moved-from objects aren't allowed to be used. I've heard C++ programmers complain about Rust being less powerful than Rust in this respect, but it is simpler.
> It's trivially easy to cause use-after-free errors with lambdas.
I don't fully understand it, but from what's described at the link, this causes the use-after-free bug:
[x] () -> future<T> {
co_await something();
co_return x;
}
The fix is to write it like this, I'm pretty sure:
[x] () -> future<T> {
auto xx = x;
co_await something();
co_return xx;
}
The reason is because coroutines copy their input to their own stack frames, but lambdas are passed by address, and so the coroutine later dereferences a dangling pointer. Again, it just seems more complex than anything in Rust.
[1]: https://doc.rust-lang.org/nightly/std/pin/index.html https://doc.rust-lang.org/nightly/std/pin/index.html
- fluoridation 3y ago>If you use a moved-from unique_ptr, for example, you'll dereference a null pointer. You can still check if the pointer is null first, which would be UB if the object was simply in an undefined state. >It's something you have to keep track of. You don't have to keep track of it, you just have to design your classes such that moving a value leaves the source in a usable state. At the point of use, moving is no different from any other operation on the object. There's no intrinsic difference between moving an std::unique_ptr and reset()ing it. You do have to keep in mind the possible values of the object as you operate on it, but this has nothing to do with move semantics. In any language, you wouldn't want to attempt to access the 10th element of a list after clearing the list. Now, if you design your class such that the moved-from state is invalid and distinct from any state the the object could reach by any other means then yes, you will need to treat moves on that type differently. However, that's a problem you created for yourself. >The reason is because coroutines copy their input to their own stack frames, but lambdas are passed by address, and so the coroutine later dereferences a dangling pointer. Yes, like I said, lambdas decouple lifetime from lexical scope. You have to be use them carefully to avoid running into issues. This is not because C++ lambdas are a complex feature than Rust lambdas. The opposite is true: Rust lambdas are more difficult to use incorrectly because Rust's type system is more complex and can keep track more closely of the lifetimes of objects.