Hacker News new | past | comments | ask | show | jobs | submit login
Pointers in Rust: a guide (steveklabnik.com)
98 points by steveklabnik on Oct 18, 2013 | hide | past | favorite | 32 comments



I'm glad that this guide emphasizes avoiding pointers by default. One of the emerging trends that we've noticed for new users is to allocate on the heap far too often when they could be allocating on the stack instead. I'm also glad that this emphasizes the return-value optimization that Rust uses to make returning a pointer unnecessary.

However, Steve, I wish you hadn't mentioned managed pointers at all. :P Overuse of managed pointers is another one of those trends that we've noticed in new users, and it's our own fault for making them so deceptively easy to use (at first, anyway... you pay the cost later by making nearly everything else in your code more difficult). We're moving managed pointers out of the language and into the stdlib specifically to discourage this overuse (which will hopefully also embolden users to consider our reference-counted stdlib pointer as an alternative to the GC'd pointer).

Likewise, I don't think that it's important to mention traits in the discussion on owned pointers. Using trait objects is almost always less preferable than just using generics, as trait objects are less efficient than generics and are much more restricted. Not that they don't have their uses, but those uses are few.

In general this is great! We need more articles like this to combat the "oh rust is so complicated it has 200 DIFFERENT TYPES OF POINTERS" propaganda. :)


I'm still pretty unexperienced with the language so take this with a grain of salt, but I think one of the reasons people allocate on the heap is because it is so tempting to write functions that accept borrowed pointers as arguments, due to the flexibility of taking either borrowed or managed pointers. However, when you want to call the same function using a stack variable, you have to change your call invocation or the function, which is just an extra pain whenever you want to refactor.

For example:

    fn foo(a: &int){
      a.do_something()
    }

    fn main(){
      let a = @5;
      let b = ~5;
      let c = 5;
      foo(a);
      foo(b);
      foo(c); // ack!
      //laboriously hunt down all my invocations
      foo(&c); //much better
    }


First I just want to point out that you're not allocating on the heap just by using references. This:

  let x = &1;
...is just sugar for:

  let x1 = 1;  // allocated on the stack
  let x = &x1;  // reference to a stack allocation
Currently you only allocate on the heap when you use either ~ or @ pointers. And if you're writing an API for widespread consumption, then writing functions that take arguments by reference is actually encouraged since it gives the caller the maximum amount of flexibility.

As for the pain of refactoring your code to move from heap-allocations to stack-allocations, this is exactly why we're so fervent to encourage people to use stack-allocated values from the get-go. :) We're fans of Python's philosophy of making things explicit, which explains why we're different from C++ in this particular detail (i.e. we make you explicitly acknowledge when you're passing a reference to a function that expects a reference).


> I wish you hadn't mentioned managed pointers at all

I originally wrote this as an official guide, so I needed to talk about them, and then I got lazy, and just posted it after the PR got closed. But yes, discouragement is the right approach.

> We need more articles like this to combat the "oh rust is so complicated it has 200 DIFFERENT TYPES OF POINTERS" propaganda.

Exactly. The problem is that pointers are one of the more neat features of Rust, and most people who are new to programming languages like unique[1] features, so it got talked about a lot, so people assumed you needed to know about all these details, when that's not actually true.

I know we all over-used `~str` for a long time...

1: ;)


~str used to be the only string type we had! And vectors were all on the heap and dynamically-sized!!

Kids these days, with your fixed-size arrays and your static strings and your global variables and your hoity-toity region analyses.


I'm not sure why people think Rust pointers are so confusing. Any C++ developer familiar with shared_ptr and unique_ptr should understood Rust pointers almost immediately.

The area of Rust that I find most confusing (and sends me back to the documentation and example code every time) is the module system and the relationship between library crates, modules, files, and identifier paths. :\


Managed pointers are fine. The only problem I see is ~str and ~[] are managed pointers most of the time and that makes the code looks weird to people.

If we make str default to managed rather than ~str, and [] default to managed rather than ~[], most of the weird pointer complaints will go away.

Edit: Darn, what was I thinking? I meant owned pointer.

Strings or vectors are owned in most cases. That makes Rust code littered with ~str or ~[], especially when having vector of strings or vectors of vectors. ~[~[~str]]. If we make str and [] default to owned, the code would be much cleaner.

That means move the syntax for fixed vec to something else, like ![1,2,3]


I'm not sure what you mean. "String literals" are &str, not a managed string, and [1, 2, 3] syntax creates a fixed-length vector, not a managed one.


Neither ~str nor ~[] are managed. Neither of those introduce any runtime overhead.


I've been learning Rust officially for about a week. I wish this had made it on hacker news last Friday!

I'm learning by making a little MVC framework with cgi-bin (eventually it'll be portable to rust-http and elsewhere..). The best part about Rust is that in 99% of cases, if your "logic" is right, and your code compiles, everything will just work and run properly. Unlike in C where your logic may be right, but your pointers wrong, or in C# where your logic is right, but that threw a null reference exception, etc.

The hard part was that I tried writing Rust like I do C#. Using garbage collection elsewhere. The biggest hurdle is that when you're designing your structures you must not think just about what they need to access. You must also think about the ownership of what they access, and the ownership of what will be using it.

After finally refactoring and figuring out my ownership model, turns out I didn't even need garbage collection or reference counting. Everything could go onto the stack save for some vectors and mutable strings


Hopefully future new learners will have it easier. :)

If there's anything else that's confusing that could use elaborating, let me know.


These guidelines pretty much apply to C++, although pointers in Rust are more akin to smart pointers than raw pointers, and there are indeed a bunch of use cases for those. Sticking to values and references works pretty well most of the time though.

But pointers in Rust seem pretty well thought out and more sophisticated than what's possible in C++. I'm particularly excited about the compiler error when a borrowed pointer goes out of scope.


Yup! If you use C++, this wiki page can help: https://github.com/mozilla/rust/wiki/Rust-for-CXX-programmer...

Basically, imagine boost's pointer types being in the language, so that the compiler can reason about them.


I love it. I'm staring to look for reasons to try Rust in a project, luckily they're piling up :)


Supposedly 1.0 is "around the corner," so maybe you'll find a good reason by then...


> These guidelines pretty much apply to C++

Yep, C suffers from pointer abuse and with it, infects any language that uses C compatibility as language subset.


There's also a discussion on the (very active) /r/rust : http://www.reddit.com/r/rust/comments/1opo36/pointers_in_rus...

There's some good pointers about pointers there. ;)


I evaluated Rust a couple of months ago and had to reject it because it's pointy nature makes it difficult to write high order functions.


What made it so hard? I'd be interested in hearing more.


Unfortunately I don't still have the code I was writing to tell you exactly, but the main issue is that you can't write generic functions because some times your usage will need to pass in a reference, some times not. The map and filter functions are examples of this.

My hello world for evaluating a language is this: write a program that sums its arguments. It's arguments might be numbers or strings so you have to filter and cast. It's a simple program but I find it a good indicator of how elegant (in terms of what I consider elegant of course) a language is to use on a daily basis. Lisps obviously do best in this type of test but Haskell also does very well despite it's type system. So the issue isn't types, it's pointers.


> Unfortunately I don't still have the code I was writing to tell you exactly, but the main issue is that you can't write generic functions because some times your usage will need to pass in a reference, some times not. The map and filter functions are examples of this.

Usually you pass references, because that's the most generic option. I wouldn't say "you can't write generic functions", but you sometimes have to add wrappers that convert between levels of indirection.

> My hello world for evaluating a language is this: write a program that sums its arguments. It's arguments might be numbers or strings so you have to filter and cast. It's a simple program but I find it a good indicator of how elegant (in terms of what I consider elegant of course) a language is to use on a daily basis. Lisps obviously do best in this type of test but Haskell also does very well despite it's type system. So the issue isn't types, it's pointers.

Certainly a language can be easier to use and you can write more elegant-looking code if most everything is a reference. No argument there.

It basically sounds like you weren't using the low-level features of Rust, which are important for our use cases but not important for everyone's. So Rust might well not be for you. That's totally fine, as I don't intend for Rust to be the one language to rule them all.


"It basically sounds like you weren't using the low-level features of Rust, which are important for our use cases but not important for everyone's."

But even in a system level program, how often do you need low level features of the language. I think the answer is "very seldomly", and hence, these low level features should be tucked in behind the higher level abstractions. They should be available only when required.


Ruster here, and this is also something that concerns me (I too have run afoul of the map vs filter issue that you mention). We'll need to carefully consider our composable higher-order-function APIs before 1.0.


I think an approach like Clojure's reducers might work, since it's possible to compose several higher-order operations and end up with no extra allocations or loops.


What language did you choose in the end?


Lua


What kind of project was it that both Lua and Rust were in the running? Those are pretty much on opposite ends of spectrum, at least the spectrum of imperative languages with functional parts.


Don't forget that you can embed Lua in a collection of C libraries, and thus: do system programming with it.

;)


So I don't speak Rust, but if succ takes a pointer then yes you do need to pass it a pointer, and that's what referencing is doing, right? Not that referencing a stack variable is hard...


That's correct.


off-topic : please pardon me, does rust support multi-parameter typeclasses? a link with example would be nice. docs don't go into typeclasses much.


Can you give me an example in another language?




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

Search: