3 ms·
The Rust borrow checker checks borrows: that is, references. This doesn't emulate borrows, but rather emulates Rust-style moves. An attempt at a more accurate
by pcwalton 10y ago
The Rust borrow checker checks borrows: that is, references. This doesn't emulate borrows, but rather emulates Rust-style moves.
An attempt at a more accurate statement would be that this emulates the use-after-move checking that the Rust compiler does. The problem is that that it doesn't do that either: it statically prevents copies but doesn't prevent use-after-move.
I think the accurate way to describe this pattern would be that it disallows copies and forces you to annotate moves with the move keyword. This is somewhat similar to what Rust does, in that non-Copy types are moved by default. The difference is that you don't have to write "std::move" in Rust: the compiler just infers the right thing to do.
It's a little hard to map this onto Rust semantics to begin with, since fundamentally all this is is not having a copy constructor, which is a concept that doesn't exist in Rust in the first place.
- masklinn 10y ago> The difference is that you don't have to write "std::move" in Rust: the compiler just infers the right thing to do. And of course since it's the language's default you don't have to write a dozen lines of boilerplate either, this is the C++ code stripped of the interspersed text: class MoveOnlyInt final { private: int i; void release() { /* call to release function here */ } public: explicit MoveOnlyInt(int i) : i(i) {} ~MoveOnlyInt() { release(); } MoveOnlyInt() = delete; MoveOnlyInt(const MoveOnlyInt &) = delete; MoveOnlyInt& operator=(const MoveOnlyInt &) = delete; MoveOnlyInt(MoveOnlyInt &&other) { i = other.i; other.i = -1; } MoveOnlyInt& operator=(MoveOnlyInt &&other) { release(); i = other.i; other.i = -1; return *this; } operator int() const { return i; } }; This seems like a fair Rust version, with as you noted the "move source" becoming inaccessible rather than just being in an invalid state: struct MoveOnlyInt { i: isize, } impl MoveOnlyInt { fn new(i: isize) -> MoveOnlyInt { MoveOnlyInt { i: i } } } // Rust doesn't have an "operator int", this seems close enough impl<'a> From<&'a MoveOnlyInt> for isize { fn from(m: &'a MoveOnlyInt) -> isize { m.i } }
- kibwen 10y agoThat's actually even way too much code for the Rust version. A wrapper type declaration is as simple as `struct MoveOnlyInt(i32);`, and construction is then just `let foo = MoveOnlyInt(42);`. (And of course, the wrapper type is only necessary because integers in Rust implement `Copy`, opting out of move semantics.)
- steveklabnik 10y ago(and "operator int" would be foo.0 in this case)
- mrout 10y agoIt's actually far too little code, as it leaves out all the code in the Rust compiler that implements this.
- heinrich5991 10y agoDon't use `isize`, `isize` is not equivalent to C's `int`, but `intptr_t`. Specific sizes would be best, but if you need to copy C's `int`, there's `libc::c_int`.
- Animats 10y agoRight. This isn't about borrowing; it's about taking ownership. Single ownership is half the problem. Enforcing borrow semantics means passing references/pointers you can use, but can't keep or delete. Trying to do this in C++ means either extreme restrictions on not being able to copy a reference/pointer, or the possibility of some function keeping a reference/pointer beyond the lifetime of the object. There's no way to say "it's OK to pass or copy this to a scope with a shorter lifetime". The C++ type system doesn't understand "inner scope". Trying to retrofit memory safety to C++ without breaking backwards compatibility is hard. I struggled with this years ago. It turns out it's in some ways easier to do for C than for C++.[1] [1] http://www.animats.com/papers/languages/safearraysforc27.pdf http://www.animats.com/papers/languages/safearraysforc27.pdf
- bhuztez 10y agotake a look at mine ( https://github.com/bhuztez/borrow https://github.com/bhuztez/borrow )