Remix.run Logo
hn45e7pbij a day ago

I'd push back slightly on move — at small scale the opposite has been true for me.

dalvrosa a day ago | parent | next [-]

Not sure what you mean, but std::move is one of the greatest tools in C++

pdpi 3 hours ago | parent | next [-]

This is one case where Rust benefited from C++’s experience — move by default with opt-in clone/copy is IMO the better setup.

tialaramex an hour ago | parent | next [-]

This is an easy mistake to make but it's not what happened

Programmers already knew (~20 years ago) when the C++ move feature was designed that what people want is the destructive move assignment semantic, the thing Rust has today. Other languages did have that. But C++ 98 already existed and WG21 already did not want to make it difficult to take your crusty 10+ year old C++ codebase, slap a sticker on it and say this is "Modern C++"

The C++ "move" proposal is slightly sneaky, it admits that what they're proposing is not the destructive move (again, people know they want this) but it gives the impression that if they really want destructive move they can add it later, without revealing what's really going on underneath.

In fact C++ move is roughly what Rust would call core::mem::take, we move something in the usual way (a destructive move) but then we replace it with some value of the same type, in the case of core::mem::take it's Default::default()

To enable this, C++ is full of types which look superficially familiar to a Rust programmer but have a weird "empty" state to provide that default value where none would make sense. For example std::unique_ptr<T> looks like it's Box<T> but it's not, it's actually Option<Box<T>>, even newer types often do this but they might be more embarrassed about it.

[Edited to clarify timeline]

sprocketz 3 hours ago | parent | prev | next [-]

And the most important idea: destructive moves. Since C++ doesn't track lifetimes it has to leave the object in a "valid state" after a move and the destructor still runs which has to have a check if it should do something or not.

pornel 2 hours ago | parent [-]

BTW, Rust's lifetime annotations for borrowed references are a mostly orthogonal feature.

Liveness of objects for move/drop semantics is tracked differently, without any syntax and with implicit runtime drop flags where necessary.

C++ could probably add the same deinitialized/moved-from state tracking (with an opt-in for back compat sake) purely to avoid dtor bloat, without having to add safety of borrow checking.

bluGill an hour ago | parent [-]

> C++ could probably add the same deinitialized/moved-from state (with an opt-in for back compat sake) purely to avoid dtor bloat, without having to add safety of borrow checking.

There is a lot of talk in the C++ committee about this. The details are complex in some obscure cases.

affenape 2 hours ago | parent | prev [-]

It did for sure, but the problem with C++ is its heritage, specifically that structures can be self-referential. For instance, the Rust's url::Url type has to use usize offsets for tracking the location of each of its components. Conversely, in C++, someone could have already created a similar Url type that would use std::string for the buffer and char pointers for the component locations. As such, you cannot simply memcpy from one struct into another and forget the former as std::string could have its own in-place storage and that would invalidate all pointers - you'll need to define a move constructor instead.

bluGill 3 hours ago | parent | prev [-]

std::move is a great tool when used correctly. However used incorrectly it makes code worse: more verbose and less performant. Since I have no idea how you are using it I can't comment on your experience. My experience is people (including me!) get it wrong fairly often. Fortunately tools can detect a lot of cases where you get it wrong.

sprocketz an hour ago | parent [-]

My little trick is to think of them as "oh, I accidentally made an lvalue from an actual rvalue here because a name was introduced, so I need to cast (i.e move() or forward()) back to an rvalue again", that's why i have them as macros: MOVE_CAST and FORWARD_CAST defined as static_cast (also avoids blowing up compile times). I never think in terms of "moving this object".

jplusequalt 2 hours ago | parent | prev [-]

Thanks Claude.