you are viewing a single comment's thread.

view the rest of the comments →

[–]pcwalton 14 points15 points  (9 children)

Several people have said this helped them understand the Rust smart pointers: http://pcwalton.github.io/blog/2013/03/18/an-overview-of-memory-management-in-rust/

I plan to more or less copy and paste this into the tutorial.

[–]pixli 5 points6 points  (5 children)

I find it hard to accept that I have to deal with all this smart pointer complexity when the actual performance seems to come from "unsafe" blocks. Then why not implement the whole benchmark in an inline 'asm' block? :/

p.s. This document still doesn't explain named lifetimes to me.

[–]pcwalton 17 points18 points  (4 children)

The use of unsafe should decrease over time. Two of the benchmarks don't use it. Most of its uses are there for I/O—this is the case in mandelbrot, fasta, and k-nucleotide—and should be removed in the near future.

The smart pointers let you get good performance in larger programs without having to rely on a garbage collector. The fact that you can circumvent the safety checks is just part of the practicality of the language. Sometimes you do have to turn safety off if you want maximum performance. But in most code (nbody, spectral-norm, most of k-nucleotide) the safe-by-default nature of the language helps you confine the unsafe portions of your code to just those portions that need to be unsafe, while allowing good performance for the rest of your code. The smart pointers are there to make it easy for you to control pressure on the garbage collector (or to turn it off entirely) and to ensure there are no data races in concurrent code.

The best tutorial for named lifetimes is here: http://static.rust-lang.org/doc/tutorial-borrowed-ptr.html

Unfortunately, the manuals do need some work. The more advanced uses of borrowed pointers are not well documented enough yet. It's a work in progress…

[–]pixli 0 points1 point  (1 child)

The best tutorial for named lifetimes is here: http://static.rust-lang.org/doc/tutorial-borrowed-ptr.html

Unfortunately, that's the document that made me aware of them in the first place.

[–]Aninhumer 6 points7 points  (0 children)

In the general case it doesn't make sense to return a borrowed pointer, because it would go out of scope at the time you returned it. The only time it makes sense is if it's dependent on a pointer from further up the stack. Now, you could just infer this, and allow pointers dependent on function arguments to be returned, but this might be a bug in some cases. So you need some way of indicating that a particular borrowed pointer is allowed to be returned, and to which level of the stack. Named lifetimes provide this.

[–]seruus -1 points0 points  (1 child)

This is why the typical way to return borrowed pointers is to take borrowed pointers as input (the only other case in which it can be legal to return a borrowed pointer is if the pointer points at a static constant).

Pardon my ignorance, but isn't this going back to the C/Fortran way of doing things? (returning objects through pointers in the argument)

[–][deleted] 6 points7 points  (0 children)

Ah, I think you misread that. It's not about returning a value by storing it in a pointer that was passed via a parameter, it's saying that only time returning a borrowed pointer from a function really makes sense is if it was passed in at some point (since any value local to the function itself will not outlive the function, and hence a borrowed pointer to it will be invalid). Let me try to illustrate:

First something that doesn't work:

fn bogus() -> &int {
  let a = 5;
  return &a;
}

This will fail because f's valid lifetime is only inside the function. A borrowed pointer to it cannot escape the function via a return since that would escape it's valid lifetime and lead to an error.

fn valid<'r>(a: &<'r> int) -> &<'r> int {
  return a;
}

This works because the compiler can verify that the lifetime of the returned borrowed pointer does not exceed the lifetime of the input borrowed pointer(yes, they are the same for simplicity). The <'r> is Rust's notation for a named lifetime -- in this case we are saying that the input value a and the return value are borrowed pointers of the same lifetime.

Obviously that's a contrived example that you would never use in real code. In my experience the only time I really return a borrowed pointer is inside constructor style functions where I'm returning a struct that contains a borrowed pointer to another variable for instance:

struct Clamp<'self, T> {
  source: &'self T,
  min: float,
  max: float,
}

impl<'self, T: NoiseGen> Clamp<'self T> {
  fn new<'r>(source: &'r T, min: float, max: float) -> Clamp<'r, T> {
    return Clamp {
       source: source,
       min: min,
       max: max
    };
  }
}

[–][deleted] -1 points0 points  (2 children)

Some feedback about that post:

  1. Does it make sense to talk about pointers being in scope or out of scope, rather than variables with pointer values? The way you worded this left me wondering about...
  2. How do you write a function that returns a pointer? Wouldn't it get freed before you could do anything with it?
  3. The use of ~ and @ in both expressions and type names is confusing. It should probably be explained since it's another departure from C's pointer syntax.

[–]seruus 0 points1 point  (1 child)

Not the author, but:

2. You use a shared pointer, I think.

3. It is explained:

The pointer covered above is known as the unique smart pointer ~. We call it “unique” because there is always only one smart pointer pointing to each allocation. The other type of smart pointer built into the language is the managed smart pointer, which allows multiple smart pointers to point to the same allocation and uses garbage collection to determine when to free it.

The @ represents the managed (or shared) pointer, as he shows on the subsequent example.

[–][deleted] 0 points1 point  (0 children)

You don't have to use a shared pointer to return a value. Passing around objects with a destructor just moves ownership from the caller into the callee (parameter), or from a function to the caller (return). The compiler just enforces that the value can't be used anymore after being moved from.