Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Great.

template <typename T> void drop(std::unique_ptr<T> &&) {}

Also, you don’t really need this because of RAII. You can make it simpler, and here’s how you’d do this in C++26.

template <std::movable T> void drop(T &&) {}

It can get even simpler!

void drop(std::movable auto){}

Do you see my point about C++ incorporating the good ideas at a glacial pace?



Edited to inject: Actually on second thoughts maybe none of these even do what you thought they did, they're even sillier than I'd assumed.

So funny thing, your C++ 26 solution doesn't do what my Rust function does.

Actually even the first one doesn't, but you likely don't really care about a std::unique_ptr, so it doesn't feel like a difference, and the negligence only really bites when it's not "just" that pointer type.

You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.


> You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.

Perhaps your idea of C++ semantics is a bit off?


After more consideration I think probably your functions don't do anything at all?

Is that the joke here? That despite everything you didn't understand why core::mem::drop has that definition and so reading the empty body you assumed that you can just not do anything and that'll work in C++ ?


Are you trolling? Or do you genuinely not understand why it might work in C++?

https://godbolt.org/z/58TqTTM37


No, I'm not trolling, I'll give you the benefit of the doubt, try this C++:

https://godbolt.org/z/zM3oxjrfn

and contrast this Rust:

https://rust.godbolt.org/z/1rMYcqY65

In your unique_ptr<T> example what you'd hidden (from me? Or perhaps from yourself) was that we're not destroying the unique_ptr, we're just destroying the Foo, and since the unique_ptr is null the destructor for that will be silent when the scope ends.


> template <typename T> void drop(std::unique_ptr<T> &&) {}

So what happens for objects that aren't behind a unique_ptr?

> Also, you don’t really need this because of RAII.

drop() indeed isn't used much because of RAII, but it is handy for those instances where you do actually want to dispose of something "early".


See the other versions I added.

> dispose of something "early".

This is a bit of an anti pattern in C++, but doable of course.


It’s not just for disposing things, it’s also used to decompose things into their constituent parts… like if I had something representing an HTTP response and it contains headers and a body, I could write a `fn into_parts(self) -> (Headers, Body)` that returns the parts you care about while destroying the response object.

This is useful in the grpc library I use, which has a response type with some metadata about the response, and an “inner” value that represents the thing the grpc method returned. If you just want the inner thing and don’t care about the metadata, there’s a `fn into_inner(self)` that destroys the response and leaves you the inner value.

You can write similar methods in C++ but they require leaving the original value in an “empty” state so that you can avoid expensive copying of things while still letting the moved-from value be “safe” to use. But that affects your API design… you now have to make the methods that fetch the “inner” thing be nullable/optional so that you can represent “oh this was moved from so there’s no body any more”. You don’t have to do that in Rust: moves are just a memcpy and the compiler will reject programs that use the moved-from value.


> compiler will reject programs that use the moved-from value.

Yes, this is something the C++ 'language' is not going to specify other than claiming that it is undefined behavior.

Doesn't prevent compilers from doing it though, clang will happily do this for you right now in most cases.

https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-a...


But it's not undefined behavior. That's the key. It's "unspecified behavior", meaning that according to the standard, it's allowed. A program that reuses a moved-from value is considered valid and well-formed by the standard. It merely delegates how to make this work to the individual implementation (a string becomes an empty string, a vector becomes an empty vector, etc.)

The RFC you posted has nothing to do with move semantics, it's about references outliving what they point to (ie. use-after-free, etc) and similar to Rust's borrow checker.

But here's the thing: move semantics and the borrow checker have nothing to do with each other! The borrow checker ensures that borrowed data (ie. &Foo, equivalent to C++'s references) is sound, it's not the part that enforces move semantics. That happens earlier in the compilation, the compiler enforces moves well before the borrow checker phase.


> See the other versions I added.

I don't think those work either. Not only do neither of those actually end the lifetime of what's passed in, but they have other flaws as well.

> template <std::movable T> void drop(T &&) {}

This literally does nothing. Reference parameters don't affect what's passed in on their own - you need at least something on the other end (e.g., a move constructor) to do anything.

For example, consider how this would be instantiated for std::vector<int>:

    template <> void drop(std::vector<int>&&) {}
> void drop(std::movable auto){}

I believe this is equivalent to passing by value, so this will actually invoke a copy if what's passed in isn't eligible for a move (or if the move constructor is equivalent to the copy constructor, since copyable subsumes movable). Again, consider how this would be instantiated for std::vector<int>:

    template <> void drop(std::vector<int>) {}
> > dispose of something "early".

> This is a bit of an anti pattern in C++, but doable of course.

I don't think you can do quite the same thing in C++ without introducing new scopes.


> Not only do neither of those actually end the lifetime of what's passed in

>For example, consider how this would be instantiated for std::vector<int>:

Great, here you go.

https://godbolt.org/z/9v66n6Ta4

And yes, the compiler will not prevent you from using this value. (clang will eventually, I think)

But clang static analyzer will happily detect it.

https://stackoverflow.com/questions/72532377/g-detect-use-af...


> Great, here you go.

As I pointed out, your drop_new is broken for copyable types. For example, consider std::array:

    auto w = std::array<int, 3>{0, 1, 2};
    drop_new(std::move(w));
    std::cerr << w[0] << ", " << w[1] << ", " << w[2] << '\n';
This prints "0, 1, 2". Rust's drop() doesn't suffer from this flaw.

> And yes, the compiler will not prevent you from using this value.

Yes, that is the point!

This bit:

    std::vector<int> vec {1, 2, 3};
    drop_new(std::move(vec));
    std::cerr << vec.size() << " <- ?\n";
Simply does not compile in Rust [0]:

    let vec = vec![1, 2, 3];
    drop(vec);
    println!("{0} <- ?", vec.len()); // error[E0382]: borrow of moved value: `vec`
[0]: https://rust.godbolt.org/z/GjcMYnEzq

> But clang static analyzer will happily detect it.

One problem is that you're not guaranteed to catch it, similarly to why static analyzers aren't guaranteed to catch use-after-frees.


I'm not sure why you keep posting snippets demonstrating C++ rvalue references. We all know what those are, and it's not what we're talking about.

We're talking about how the rust compiler uses move semantics to prevent you from using the moved-from value, such that code like this will not compile:

    let f = Foo::new();
    drop(f);
    f.foo(); // Error: use of moved value
C++'s move semantics do not prevent you from using f after you've moved it. On the contrary, it's intentionally allowed. It's not undefined behavior either, it's "unspecified" behavior, which means "behavior, for a well-formed program construct and correct data, that depends on the implementation". This simply means that it's up to the individual type to decide what happens when a value is moved from. (A string becomes an empty string, for instance, or a vector becomes and empty vector.)

Rust's move semantics mean:

- You don't write a move constructor

- Moves are just memcpy

- The compiler enforces the old value can't be used any more

C++'s move semantics mean:

- You must write a move constructor (rvalue reference constructor)

- Moves are arbitrary code

- The language explicitly allows the moved-from value to continue to be used

That there are certain linter-style tools like clang-tidy which can be configured to warn on using moved-from values is irrelevant: The standard explicitly allows it. It's personal preference whether you should make a habit of using moved-from values in your codebase, which is why this will only ever be a linter thing. The C++ standard would have to completely change its mind and retcon moves to mean something different, if they ever wanted to change this.

Now, the beginning of this thread was someone saying "Rust is basically the wanted fixes to C++ that C++ itself could never adopt for legacy reasons". Then you came back with "I agree with your point except for the 'never' qualifier", implying C++ will eventually support Rust's ideas. But move semantics in C++ are precisely the opposite of those in Rust, because rust-style semantics were deemed impossible to implement in C++, even though it's what people actually wanted at the time. So I think it's fair to say C++ will "never" get Rust-style move semantics.




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

Search: