3

Some languages, like Haskell, make no distinction between pass-by-value and pass-by-reference. The compiler can then approximately choose the most efficient calling convention with a heuristic. One example heuristic would be for the Linux x64 ABI: if the size of parameter is greater than 16 bytes, pass a pointer to the stack otherwise pass the value in registers.

What is the advantage of keeping both notions of pass-by-value and pass-by-reference (non-mutable of course) in Rust and forcing the user to choose?

Could it be the case that pass-by-value is syntactic sugar for pass-by-reference + copy if the value is seen to be modified?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Łukasz Lew
  • 48,526
  • 41
  • 139
  • 208

2 Answers2

12

Two things:

  1. Rust will transform certain pass-by-value calls into pass-by-reference, based on a similar heuristic.
  2. Pass-by-value indicates ownership transfer, while pass-by-reference indicates borrowing. These are very different, and totally orthogonal from the asm-level concern you're asking about.

In other words, in Rust, these two forms have different semantics. That doesn't preclude also doing the optimization, though.

Steve Klabnik
  • 14,521
  • 4
  • 58
  • 99
0

[Edited: changed exampled to work in release mode]

It isn't syntactic sugar, as one can see by looking at the generated code.

Given these functions:

fn by_value(v: (u64, u64)) -> u64 {
  v.0 + v.1
}

fn by_ref(v: &(u64, u64)) -> u64 {
  v.0 + v.1
}

then if one was syntactic sugar for another, we'd expect them to generate identical assembly code, or at least identical calling conventions. But actually, we find that by_ref passes v in the rdi and rsi registers, whereas by_value passes a pointer to v in the rdi register and has to follow that pointer to get the value: (see details, use release mode):

by_value:
  movq  8(%rdi), %rax
  addq  (%rdi), %rax
  retq

by_ref:
  leaq  (%rdi,%rsi), %rax
  retq
rp123
  • 3,623
  • 1
  • 18
  • 20
  • 1
    You do not seem to be compiling with optimizations. Using [this version of your code](https://play.rust-lang.org/?gist=6fd877dbbaa0cded95220a00afc1b6e2&version=stable&backtrace=0) (note all the gyrations to prevent the optimizer from removing the function entirely) and *compiling in release mode*, both functions compile down to the assembly `leaq 1(%rdi), %rax; retq`. Note all the junk about pushing and popping registers has been optimized away. – Shepmaster Apr 12 '16 at 02:48
  • You're right, in optimized mode they become the same. I've now changed to a slightly more involved example which shows different behavior. – rp123 Apr 12 '16 at 03:41
  • With the most recent change, you aren't comparing apples-to-apples. [Checking the size of the values](https://play.rust-lang.org/?gist=02d1017d548d2d5ea6ee0cb6cf25a511&version=stable&backtrace=0) shows that a tuple of two `u64` is 16 bytes, the reference is 8 bytes. It's entirely possible that the optimizer has decided that 8 bytes is better than 16. – Shepmaster Apr 13 '16 at 00:56