Live data from Hacker News

Ask HN: How do I understand Rust?

news.ycombinator.com

51–60 of 61 posts

Re: Ask HN: How do I understand Rust?

#51

I found that my brawls with Rust's borrow checker ended when I made ownership the focus of my code design. Coming from a C++/Objc/Go background I was used to creating an object on the heap and holding a reference counted/garbage collected pointer to the object wherever it was used. This is shared ownership of state, a style of coding Rust considers to be so egregious that it is a compile error. Initially I would use…

>" Coming from a C++/Objc/Go background I was used to creating an object on the heap and holding a reference counted/garbage collected pointer to the object wherever it was used. This is shared ownership of state, a style of coding Rust considers to be so egregious that it is a compile error." Is the idea that because anyone else can come along and make reference to the same object on the heap mean "shared" in this c…

Exactly. You can have as many immutable references to an object as you want, or you can have one mutable reference to it, not both. (Each of those references is how you borrow something from the real owner, hence the name.)

The borrow checker is that part of the compiler that makes sure that these borrowing rules are followed. Other rules are implemented by other parts of the compiler; the type system has the job of making sure you don't try to write to something that you only have an immutable reference to, for example.

Re: Ask HN: How do I understand Rust?

#52

I've been doing software dev since I was a kid. In my mid-30s now. I spent almost a month with Rust about a year ago. I was very disappointed. The borrowing rules seem to have changed as the language had evolved. I found the Internet documentation to be very confusing. My conclusion was to let Rust be. It felt like coding with restraints, and as my Rust expert friends tell me, that is the way it is supposed to be. My…

Interesting. I wonder what most people's thoughts are on the market/target area for Rust. My complete outsider perspective was that Rust was a "replacement" for C, C++, Ada, etc., and that "most" programs would be better suited to be written in higher level languages.

Re: Ask HN: How do I understand Rust?

#53

> It seems like everything I know about structuring a program goes out the window when borrowing comes into the picture. Try to avoid structured programming and mutable state where possible. Apply functional programming idioms and use immutable data structures if you can. Rust adopts many features from functional programming languages: http://science.raphael.poss.name/rust-for-functional-program... Borrow checker wil…

Instead of using unsafe code, I would use RefCell first: «a mutable memory location with dynamically checked borrow rules»

https://doc.rust-lang.org/std/cell/struct.RefCell.html

Using clone() also often helps. clone() has an overhead since you copy bytes, but sometimes you need to get something working right first, then optimise.

There are other tricks if the borrow checker interferes.

Re: Ask HN: How do I understand Rust?

#55

> It seems like everything I know about structuring a program goes out the window when borrowing comes into the picture. Try to avoid structured programming and mutable state where possible. Apply functional programming idioms and use immutable data structures if you can. Rust adopts many features from functional programming languages: http://science.raphael.poss.name/rust-for-functional-program... Borrow checker wil…

The rest of the points here are excellent, but don't use `unsafe` until you have completely internalised the borrow checker rules. If your safe attempt is unsafe (and therefore doesn't pass the borrow checker), your unsafe code will be unsafe too. The only difference is that the unsafe code won't be caught at compile-time.

This is not true, in general. The borrow checker has many limitations where it cannot prove that something is safe, so it assumes it is unsafe. The classic example is borrowing disjoint slices from an array.

Re: Ask HN: How do I understand Rust?

#56
post #35
post #31

Earlier quoted context omitted.

Indirect the graph connections. This is almost always a better design anyway. Instead of storing pointers from one graph node to another, give each node an identifier and store a map from identifiers to nodes. Each node then has a set (or other collection) of identifiers it's connected to. You can use either a mutable or immutable map for this construction.

That's basically the "don't keep references into a Vec, keep indices" advise. I don't really like that because references explicitly encode the dependency in the type system. When you start juggling indices you're basically implementing poor man's malloc. You need to make sure you don't lose track of any of them. I think owned objects should have lifetimes and a "points to non-moving memory" property. E.g. a vec can…

> You need to make sure you don't lose track of any of them.

This is a fair point, but note that in any memory management scheme except garbage collected, you run the risk of having a dangling reference if you use direct pointers to other nodes. At least with node identifiers, you can safely check that the other node still exists, in any memory management scheme.

Re: Ask HN: How do I understand Rust?

#57
post #55

Earlier quoted context omitted.

The rest of the points here are excellent, but don't use `unsafe` until you have completely internalised the borrow checker rules. If your safe attempt is unsafe (and therefore doesn't pass the borrow checker), your unsafe code will be unsafe too. The only difference is that the unsafe code won't be caught at compile-time.

This is not true, in general. The borrow checker has many limitations where it cannot prove that something is safe, so it assumes it is unsafe. The classic example is borrowing disjoint slices from an array.

The borrow checker has limitations but I agree with gp: if you haven't fully internalized its rules, especially if don't understand why it is yelling at you at some point, then you should not go unsafe because you're most likely going to write memory bugs.

Re: Ask HN: How do I understand Rust?

#58

Earlier quoted context omitted.

>" Coming from a C++/Objc/Go background I was used to creating an object on the heap and holding a reference counted/garbage collected pointer to the object wherever it was used. This is shared ownership of state, a style of coding Rust considers to be so egregious that it is a compile error." Is the idea that because anyone else can come along and make reference to the same object on the heap mean "shared" in this c…

In C++ the closest equivalent is probably unique_ptr [1], which only lets one reference exist at any given time and destroys the pointer when it goes out of scope, but also allows transferring the ownership to someone else. The C++ equivalent of Rust's Rc (reference counted pointer) is std::shared_ptr. [1] http://en.cppreference.com/w/cpp/memory/unique_ptr

Thanks for the responses, these are really helpful.

Re: Ask HN: How do I understand Rust?

#59

Basics: When you're writing (or read someone else's) functions, you should be considering three types of parameters: * (&) borrowed * (mut &) mutably borrowed * () moved The borrow operator should be your default/go-to decoration. Don't use the more destructive exchanges until the compiler forces you to. As I write this out, I'm thinking this might be a small short-coming in Rust's design. I.e., read/borrowed should…

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

Reason I'm asking is 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.

Re: Ask HN: How do I understand Rust?

#60
post #59

Basics: When you're writing (or read someone else's) functions, you should be considering three types of parameters: * (&) borrowed * (mut &) mutably borrowed * () moved The borrow operator should be your default/go-to decoration. Don't use the more destructive exchanges until the compiler forces you to. As I write this out, I'm thinking this might be a small short-coming in Rust's design. I.e., read/borrowed should…

Is the borrow syntax (&x) a reference while vanilla (x) is by value? Or are both passed by reference but with different ownership affects? Reason I'm asking is 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.

> 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
Post reply on HN