Hacker News new | ask | show | jobs
by sierdolij 2728 days ago
Lifetimes - implicit/explicit semantics for how long a name is considered alive, whereas in C++ there would be a delete or falling out of scope.

Borrowing - 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. Pony does explicit consumption.

There seems to be a need for training classes in Rust that explain the development philosophy, because it's not readily apparent from the online resources to anecdotal me who's able to code in Haskell, Elxir, Erlang, Clojure, C, C++17, Ruby, Python, Go, assembly and LLVM IR.

5 comments

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 found that I actually needed to buy the "Programming Rust" book, read it cover to cover, and then go back and start programming.

When I just tried to jump in, things didn't quite click.

> 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.

Well, .clone()
> Lifetimes - implicit/explicit semantics for how long a name is considered alive, whereas in C++ there would be a delete or falling out of scope.

In Rust there's a move or a falling out of scope. Lifetimes are passive, and just describe the connection between references so that the compiler can check that they don't become dangling when values are destroyed in essentially the same places as C++ would destroy them.

> Borrowing - 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. Pony does explicit consumption.

Rust had a more pony-like model with annotations and different modes many years ago, but the model was unnecessarily complicated and was simplified to a "everything is pass-by-value" one:

- http://smallcultfollowing.com/babysteps/blog/2011/12/08/why-...

- http://smallcultfollowing.com/babysteps/blog/2012/10/01/move...

Things to know:

- Rust has no copy constructors (but does have a "this can be safely semantically duplicated by memcpy" marker trait (Copy), which cannot run arbitrary code. See https://stackoverflow.com/a/31013156/1256624 and https://stackoverflow.com/a/24253573/1256624.)

- A "move" is a memcpy (bitwise copy) of a value to a new location, where the source becomes inaccessible at compile time

- A "read-only" T is a separate type &T, which is a value in its own right (and &_ implements Copy, so can be passed-by-value multiple times without explicit copies).

- Every value of type T is always a fully-fledged T, with all of T's operations available

- Parameters of type T (e.g. fn f(x: T)) are thus full values

- For an arbitrary type T, there's no way to (implicitly) copy values of type T, so the only way to call a function with a T parameter is to have the callee take ownership/responsibility for the caller's T value (i.e. move it into the call)

> 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.
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...

> I am not sure what you are trying to say. [Couple paragraphs rephrasing what I said]

Yep, that's what I was trying to say. (Although I'd hadn't remembered that the Copy trait actually enforced bitwise-exact-copies-only, because why would you want (implicit) non-exact copies.)