Rust pointers for C programmers
blahg.josefsipek.net
blahg.josefsipek.net
For an example of the difference, here's a function that consumes a 1kb buffer, returning the sum some of its contents. No pointers or references at all, everything "passed by value".
To my C programmer's eye this looks wrong, but from the output you see there is no memcpy. (Edit: this is wrong, both C and Rust do memcpy, see below comments, but I think my point stands.)
I'd be shocked to see a C programmer write the equivalent code ("passing by value a big struct to a function"), while in Rust it's not only idiomatic to write that code but frequently also necessary, depending on whether the function wants to take ownership its argument or some of its contents. (Imagine a more complicated 'stuff' struct that contained embedded pointers to other things.)
I'm a little embarrassed to say I ended up looking at the output on rust.godbolt.org a lot to convince myself that the bits of code I was writing was "ok", which is something I never did when learning e.g. Haskell or Go. I think the reason here is that Rust feels so close to C that there's a bit of an uncanny valley effect, where you can mostly pretend it's C up until you hit a wall.
The same is true in Rust, though it's trickier to show on godbolt because the equivalent of `extern` is less accessible in that context.
Rust programs often put those large structs in Boxes when they need to transfer ownership of something large, which is something straight out of "C mode."
The point I (poorly) was trying to make here was that passing things by value in Rust is fairly common, for e.g. Rc<T> or Option<T>, while in the C I've seen you basically never pass structs by value. But I guess, continuing the analogy, in C++ you do pass the equivalent of Rc<T> by value.
https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else...
I felt a bit betrayed when I read them..
But also, from that thread: https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else...
For example: https://godbolt.org/g/DTMQM5 (or https://godbolt.org/g/LHMgHr with C side-by-side) (and yes, I know that char != u8, this is just for demonstration purposes)
here, you have
mov rdi, rsp
call example::hello@PLT
not a memcpy. Right? Looks the same as the C version that takes char*.So, is something like this(https://godbolt.org/g/1ugxcW) a inefficiency of the optimizer that we should hope to see fixed at some point?
> The only differences between borrowed references and raw pointers are:
There’s one more: &mut T is restrict, in C terms, as long as T doesn't contain an UnsafeCell<T>.
It’s phrased in the reference as
> &mut T and &T follow LLVM’s scoped noalias model, except if the &T contains an UnsafeCell.
“noalias” is restrict here. But it’s not on &T, it’s the system as a whole that follows the guarantees, that is, both with and without.
You still can’t have un-exclusive access with a &mut T. For more: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
(And see the stuff on that page about how some of this is still in flux. And, maybe I’ve made some mistake with my words here; this stuff is hard and not what I deal with day to day...)
In fact, looking at the compiler output (https://godbolt.org/g/kt2xfW), it looks like only &T has noalias, and not &mut T. It does go away in the presence of UnsafeCell, but for &mut T it was never there in the first place.
So either that's a place where we can still add more noalias annotations, or it's off for some subtle reason I'm not familiar with.
If you switch the compiler version on my godbolt link to beta, the &mut i32 and &mut UnsafeCell<i32> both have the noalias attribute, so looks like it's on track. :)
Are *const T and *mut T also restrict in C terms? (If not, why not?)Having unchecked pointers is important for Rust to be a systems programming language. But, since they're unchecked, they may alias. That's just the nature of not being able to make guarantees.
Also, if they were restrict, it would be UB in rust, which I think is something Rust tries to avoid where possible.
(I mean, it’s not like this is 100% nailed down yet...)
However, for ergonomic reasons, Rust wants you to be able to use a Box<T> or Rc<T> in the same way you would a &T, i.e. dereferencing with *t returns the underlying value in every case. Therefore, Rust allows you to overload the dereference operator with the Deref trait, e.g. the implementation of Box [0] does a double dereference to access the inner struct member.
Note that this is distinct from "auto-deref" (or deref coercion), another Rust feature that will automatically dereference pointers as necessary in certain cases, like calling a method on a struct (so there is no arrow operator "->" in Rust like in C++). See the Book [1] for more details.
[0]: https://doc.rust-lang.org/src/alloc/boxed.rs.html#536-542
[1]: https://doc.rust-lang.org/book/second-edition/ch15-02-deref....
The reference counts are necessarily stored on the heap at the target of the pointer (the reference count must be shared between all the pointers) pseudocode:
struct RcBox<T> {
strong_count: usize,
weak_count: usize,
value: MaybeInit<T>,
}
Additionally, when you use `into_raw`/`from_raw` you're actually getting a pointer to the value member of the `RcBox` struct (ie. the reference count is stored at a negative offset from that pointer).There isn't, though. Box and Rc have the same number of levels of indirection as the built-in pointers: one.
The double * you see in the Box source you linked only arises because the deref method takes self by reference, the same way unique_ptr's operator* takes this as a pointer. (And further, that implementation looks circular? I believe that part of Box is still built-in.)
These are both functions that should always be inlined (in fact the unique_ptr implementation I'm looking at goes through at least four functions to retrieve the actual pointer) so any extra address-taking and dereferencing you see is merely compile-time bookkeeping.
Is this actually distinct? The docs for the reference type explicitly says that &T implements Deref for T, so it seems like it's the exact same thing to me.
One is "overload star", the other is "automatically insert & and star", if I'm understanding the distinction that your parent is making.
[1] https://i.redd.it/moxxoeir7iqz.png
[2] https://www.reddit.com/r/rust/comments/74yrdp/rust_container...
[1] https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd...
While this is true, I believe there's ongoing work to allow allocating [T] (and other dynamically sized types) on the stack under certain circumstances, using alloca. Which is quite nice since this was an area where Rust lagged behind C.
However, in the end, it's all just binary: that doesn't mean that using different phrasing doesn't help understanding. If it helps it make sense to the OP I'm all for it.
Rust is complex because it makes you worry about a bunch of stuff that other languages allow you to forget. But this also means that it allows you the control to create simpler things than can be created with the tools that make you feel like everything is simple.
I’m also not a fan of the crazy chained functions everywhere, and making ? do an implicit return makes it hard to visually scan code for control flow.
I’m hoping Rust matures into a better language. For now I think C++ is still far more productive and ergonomic, and there are ways of making it safe too.
Also I'm not sure how ? obscures control flow to the same level as C++, which propagates exceptions with no syntactic marker at all.
Also I’m quite sure Box doesn’t. I’ve seen compiler errors when attempting to pass one via mpsc.
If you're trying to send a value to another thread, Rust will only stop you if the author of some type somewhere inside it has opted out, and none of its containers ever opted back in (as Box does).
The error message does drill down and tell you the actual type causing the lack of Send impl.
The only time you have to manually impl Send is for custom container types that are built from raw primitives.