Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

> I still don't understand how or why a non-reference is implicitly consumed by passing it (read-only intention) by value(?) to another function and then can't be used again.

By default the value is moved in Rust, just like with std::move in C++17, which you say you know. This is to avoid performance issues when you pass complex structures, such as vectors, around. If you want to copy your value, you have to call .copy() explicitly.



sort by: page size:

> Is the borrow syntax (&x) a reference while vanilla (x) is by value? Or are both passed by reference but with different ownership affects?

This is briefly addressed in the FAQs:

"What is the difference between passing by value, consuming, moving, and transferring ownership?

These are different terms for the same thing. In all cases, it means the value has been moved to another owner, and moved out of the possession of the original owner, who can no longer use it. If a type implements the Copy trait, the original owner’s value won’t be invalidated, and can still be used."

https://www.rust-lang.org/en-US/faq.html#what-is-the-differe...

There are more details in the chapter on the stack and the heap from the book (https://doc.rust-lang.org/book/the-stack-and-the-heap.html)...

"The stack is very fast, and is where memory is allocated in Rust by default."

"What do other languages do?

Most languages with a garbage collector heap-allocate by default. This means that every value is boxed. There are a number of reasons why this is done, but they’re out of scope for this tutorial. There are some possible optimizations that don’t make it true 100% of the time, too. Rather than relying on the stack and Drop to clean up memory, the garbage collector deals with the heap instead."

So unless your data structure is boxed, it's allocated on the stack and passed by value and different ownership effects apply as well.

> I got hung up the other day on passing a String to a function that accepted &str which someone explained to me Strings dereference to &str but I think I just ended up more confused.

String literals de-sugar to &str. E.g.,

  fn borrow_str(s: &str) {}

  borrow_str("foo");         // This works
  borrow_str(String::new())  // this doesn't work

  fn take_str(s: String) {}

  take_str("foo")            // Doesn't work
  take_str(String::new())    // works
  take_str("foo".to_owned()) // works

In Rust, move semantics are assumed by default, and it's up to the caller to make a copy using .clone() if they want to keep ownership of the value (unless they know Copy is implemented and it's unnecessary, for example for integer types).

> Passing data to a function when it's not a reference means that you're giving ownership to that function.

Only in Rust, but there's no apparent reason to move ownership. If Rust didn't move ownership during pass-by-value, it would still be trivial to prove the following code correct:

    fn main() {
        let x = String::new();
        foo(x);
        bar(x);
        // x is destroyed
    }
> Maybe I'm misunderstanding the problem you're having. Do you have an example of another language that lets you do what you want?

Here's the equivalent in C++:

   int main() {
       std::string x = "";
       foo(x);
       bar(x);
       // x is destroyed
   }

By default, non-references in Rust are passed by move, although you can derive `Clone` on types composed of all `Clone` types and then explicitly call the `.clone()` method to copy things. The only types that will be copied by default are the ones that implement `Copy` in addition to `Clone`, which in the standard library is on primitives like integers and characters but overall is used fairly sparingly.

References need to be specified as mutable if mutation is needed (i.e. `&mut T` instead of just `&T`). Using immutable references is also encouraged from the borrow checking rules; having a second reference (either mutable or immutable) alive at the same time as a mutable one is a compiler error, but using multiple immutable references at the same time is fine. (Pointers are the same way, but not really used much outside of bridging with unsafe Rust, since they can be null and therefore can't be dereferenced outside of `unsafe` blocks).


Rust doesn't have syntax for passing by value vs passing by reference. The syntax you're thinking of is instead for lending vs moving, e.g. moving `Box<T>` is still passing values by reference (but owned), and `&str` is a small copyable struct passed by value (but borrowed).

It's not the surprise-copy-horror you'd expect, because there are no copy constructors, and nothing large is ever copied implicitly (you have to call `.clone()` or implement `Copy` trait for a type).

The move semantics can ensure there exists only one owning pointer to each object. It can be statically known who owns the object, and most importantly, who has to free it.


I consider C++ a copy-by-default language (and pointer/ref being the necessary evil which breaks this great default).

In Rust, it's move by default and you need an explicit .clone()

In C++, it's copy by default and you need an explicit std::move()


In the case of Rust it would typically be an immutable reference (or else passing by value wouldn't be an alternative). So the caller would have no reason to make a copy.

In C++, "moving" and "copying" are a matter of class design. While you are expected to use move (resp. copy) constructors and assignments to, well, move (resp. copy); the language doesn't rule out using them for other purposes.

In Rust, assignment and argument passing always move, "moving" always does the right thing (make a shallow copy and invalidate the original object), and you can't override this behavior. Furthermore, Rust splits what C++ calls "copying" into two concepts: shallow copying (which is the same as moving, except the original object isn't invalidated, and the type checker guarantees you can only do it when it makes sense) and deep cloning (which may be expensive and needs to be explicitly requested by the programmer).

This is why Rust can track which objects are no longer usable.


Ownership of the String is moved and the original variable cannot be used until it is reinitialised, it is not copied. Pass by value is literally always a shallow byte copy in Rust (not following pointers) and the Rust compiler must disallow further use of many variables to ensure safety.

Or move semantics including rvalue references. In retrospect, objects should have been moved by default (and destructors for moved-from objects not called), like Rust. Then copying can just be an explicit normal method (that returns its result by moving it!).

Gotcha. I thought the video would have explained this, but since it didn't get through, let me try :)

  fn take(vec: Vec<i32>)

In Rust, the default way that things operate is to _move_. So when you call take(), we'd say that the vector moves into the function.

Moving is always a memcpy. There's no way to change this behavior, so you can always know that it's true. But with a Vec, there's a subtlety: the Vec itself is three words: a pointer to the heap, a length, and a capacity. So when I say that the Vec is memcpy'd, I mean literally those three things. It doesn't try to follow the pointer and copy the data that it points to.

Incidentally, that pointer is why we say that it moves: because two copies of the Vec structure itself would mean an aliased pointer to the heap, the compiler doesn't allow the use of the older binding after take() is called.

For simpler types, like integers:

    fn take(i: i32)
they implement a trait called Copy. This means that when you call take, the i32 is copied, in the same fashion that the Vec was copied. The only difference is that there's no problem with having these two copies, as an i32 is, well, just an i32. So the compiler won't prevent the use of the binding outside the function like it would with a non-Copy type.

References are like pointers, but with the extra static checking that Rust does.

    fn take(vec: &Vec<i32>)
References also implement Copy, and so when you pass a reference to a Vec to this function, the same thing happens as in the i32 case. The reference itself gets copied.

This is sort of a long-winded way of saying that Rust is "pass by value", not "pass by reference." Some people like to call this "pass reference by value", since while it's always pass by value, references are themselves values.

What if we _did_ want to make a full copy of the Vec, including its elements? For that, we have to use the .clone() method.

    let v = ... // some Vec<i32>
    let v1 = v.clone();
Now, v and v1 will be full, independent copies of the same thing. In other words, if you don't see .clone(), it will never be a deep copy.

    let v = ... // some Vec<i32>
    let v1 = v; // a move, always a shallow memcpy
    let v2 = v1.clone(); // a deep copy
You can always see, syntactically, when you're making a possibly expensive copy of something.

Does that help? I'm happy to elaborate further.


> Borrowing

The way I think about this (which isn't quite how rust seems to) is that every value (including references) is always destroyed by passing it to a function, but gets implicitly copied if (arg is copyable && arg is referenced below). Eg:

  T a = mkT() # create value
  foo(&a) # create and immediately destroy/pass reference
  bar(a) # => bar(copy(&a)) # create-and-pass copy
  baz(a) # last use, so dont bother copying
For noncopyable values, this makes perfect sense; the callee got a value, so that value must have been moved out of the caller's variable. Treating copyable values the same way modulo the existence of a (T const ref -> T) copy function is just good consistency/orthogonality.

Literally all that Copy does is it says after assignment the moved-from variable can still be used. So in this sense, sure, these semantics are "always used". But if you don't use the variable after assigning from it, you could also say the semantics aren't used in this case. Does that help? Copy does a lot less than many people think it does.

If you're a low level person it's apparent this is because Copy types are just some bits and their meaning is literally in those bits, Copy the bits and you've copied the meaning. Thus, this "it still works after assignment" Copy behaviour is just how things would work naturally for such types. But Rust doesn't require programmers (and especially beginners) to grok that.

It's possible to explain Copy semantics first in a way that's easier to grasp for people coming from, say, Java, but that's only half the picture because your students will soon need Move semantics which are different. Thus I recommend instead explaining Move semantics from the outset (which will be harder) and only introducing Copy as an optimisation.

I think this might even be better for students coming from C++, because C++ move semantics are a horrible mess, so underscoring that Move is the default in Rust and it's fine to think of every assignment as Move in Rust will avoid them getting the idea that there must be secret magic somewhere, there isn't, C++ hacked these semantics in to a finished language which didn't previously have Move and that's why it's a mess.

I'm less sure for people coming from low-level C. I can imagine if you're going to work with no_std on bare metal you might actually do just fine working almost entirely with Copy types and you probably need actual bona fide pointers (not just references) and so you end up needing to know what's "really" going on anyway. If you're no_std you don't have a String type anyway, nor do you have Box, and thus you can't write Box<str> either, although &str still works fine if you've burned some strings into your firmware or whatever.


> An object should be accessible for reassigning a value after its move, as its often needed for algorithms, thats exactly why the C++ Standard mandates this.

The same is true in Rust. A moved-from value can be reassigned, but cannot be used (compile enforced) until it is reassigned. :)


We used to have two operators, but it wasn't actually helpful.

The only difference between a move and a copy in Rust is that you can use a copy value afterward.

Why does this matter? Okay, imagine this code:

    let v = vec![1, 2, 3];
    let v2 = v;
Since Vec does not implement Copy, it's a move. Moves memcpy the value on the stack, which, in a Vec's case, is a triple: pointer to the data, a length, and the capacity. You haven't actually copied the data on the heap, just the three pointers on the stack. If we let you use v after the assignment, there'd be two pointers to the same data. No bueno.

Compare that with this code:

    let i = 5;
    let i2 = i;
In this case, 5 is an i32, which implements Copy. The same thing happens: a memcpy. But now, the entire data was copied. There's nothing on the heap. In this case, it's totally okay to keep using i.

Does that make sense?


is always destroyed by passing it to a function, but gets implicitly copied if (arg is copyable && arg is referenced below)

I am not sure what you are trying to say. This:

  bar(a) # => bar(copy(&a)) # create-and-pass copy
  baz(a) # last use, so dont bother copying
is not possible in Rust if a's type is not a copy type. a will be moved when calling bar, so trying to pass it to baz will result in a compiler error.

The story is quite simple: a value is always moved in a function call, unless it is a copy type (the type implements the Copy trait). When the type is a copy type, a bit-wise copy is made. References are not special: immutable references are copy types. Mutable references are not copy types (otherwise, they could be aliased).

You can find an overview of all copy types in the standard library in the implementations section of the Copy trait:

https://doc.rust-lang.org/beta/std/marker/trait.Copy.html#fo...


It's kinda what Rust forces you to do, except that std::move is implied. Anything taken by value is equivalent to taking by && unless the type is explicitly marked as Copy (i.e. it can be trivially copied and the copies are implicit).

But yeah, in a c++ codebase, good modern practices are often verbose and clunky.


`Copy` implies that an object can be bitwise copied while preserving desired semantics; Rust has no exact equivalent to copy construction or move semantics in C++, which can be entirely implicit while involving custom code. (There's a 3rd-party `transfer` crate that enables a `Clone`-like marker for custom moves, however. It's used together with the Pin feature, so these moves are also explicit.)

> In C++, if you copy an object by reference, it doesn't create a copy (but risks use-after-free, Rust's borrow checker is definitely a strength over C++). If you copy by value it copies (which is an unwanted hidden allocation, but in the case of Rc/Arc, I wish Rust made incref as easy as C++).

Which has nothing to do with the question.

> The real benefit of C++ explicit captures is making it dead simple to capture exactly the state you need, and make it explicit to the reader

You could just have answered that it's the latter.

> To explicitly mark which fields you capture, you'd need to `let bar = obj.foo.bar; || {}` either in the scope of the lambda, or hide the lambda in a statement expression (creating a second level of indentation, risking rightward drift).

Oh no. Anyway.

> Additionally adding explicit lambda captures to Rust would alleviate its lack of copy semantics for smart pointers, allowing you to copy pointers into lambdas without polluting the parent scope or introducing a level of nesting: [ptr=Rc::clone(ptr)] || {...}.

That's literally just a repetition of your previous sentence.

> This would eliminate the need for gtk-rs's clone! macro for strong (not weak) references.

If it would, then you already do not need it, because you can use the precise capture pattern (a block expression around a `move` closure) in order to create your clone and the clone macro is just a convenience.

next

Legal | privacy