5 ms·
When I was learning Rust I leaned heavily on C-based analogies like this but I found they ultimately ended up being kinda harmful, because I was thinking (in C
by evmar 8y ago
When I was learning Rust I leaned heavily on C-based analogies like this but I found they ultimately ended up being kinda harmful, because I was thinking (in C mode) "do I want to pass a pointer or a value to this function" rather than (in Rust mode) "do I pass ownership or a borrow to this function".
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".
https://godbolt.org/g/icoVvz https://godbolt.org/g/icoVvz
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.
- tom_ 8y agoIn C the memcpy would be done by the caller.
- Rusky 8y agoThis is wrong. Passing a large struct by value does introduce a memcpy... in the caller: https://godbolt.org/g/26annS https://godbolt.org/g/26annS 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."
- evmar 8y agoHere it is on godbolt (using noinline): https://godbolt.org/g/7bskDc https://godbolt.org/g/7bskDc 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.
- yoklov 8y agoIt’s pretty common in both C and C++ to pass things by value, at least for more modern code. Maybe less common than in Rust, but not by that much.
- cwzwarich 8y agoIt's also common in C++ (at least in some coding styles) to pass large structs by const reference, creating the dilemma of when to pass by value and when to pass by reference, which has no perfect general solution.
- noncoml 8y agoYou are absolutely right. The Rust book makes it sound like that passing structs is free, but it’s absolutely not. It’s always bit-wise copy. Relevant Reddit and Stackiverflow discussion with godbolt examples: https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else_worried_of_performance_issues_due/ https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else... I felt a bit betrayed when I read them..
- steveklabnik 8y agoRVO and NRVO are supposed to happen, as far as I know, which is why I said it in the book. Compiler bugs do happen. But also, from that thread: https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else_worried_of_performance_issues_due/e1bt47w/ https://www.reddit.com/r/rust/comments/8ts6b4/is_anyone_else... For example: https://godbolt.org/g/DTMQM5 https://godbolt.org/g/DTMQM5 (or https://godbolt.org/g/LHMgHr 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*.
- deleted 8y ago[deleted]
- klodolph 8y agoIn C you should be equally cognizant of whether you are passing ownership or a borrowed reference. The difference is that this is not encoded in the type system. There are conventions revolving around function names that can encode this.
- Animats 8y agoThat's when you borrow. You don't need to take ownership. You only need read access, so pass as a non-mutable reference.