Rustの関数/メソッド呼び出しに関する評価順序と糖衣構文について

#### 追記 2019/12/06 17:14
@lo48576さんが借用や引数の評価順序について過去に[記事](https://blog.cardina1.red/2019/07/29/borrowck-and-builtin-compound-assign/)にされていました。
ここでは誤魔化して書いている`Two-phase borrow`や複合代入演算子での挙動についても言及されているので併せてお読みください。また[コメント欄](https://qiita.com/0ncorhynchus/items/63d2eb4e6ae905b8e423/#comment-50e423cc6960067764de)でも評価順序を利用したおもしろいコード例も提示してくださっています！


# おことわり
この記事はRustの関数やメソッドの呼び出しに関する挙動を興味範囲で大雑把に調べたものです。
また当方、`rustc`や`llvm`、`MIR`が分からない軟弱者なので、不正確な記述が**あります**。
温かい目で見守っていただけると幸いです :bow: 

# 事の始まり
先日C++を書いている時に出会ってしまった事件。
以下のようなコードを書いていました。`plus_one`は`int& x`を受け取って`x`を変更しつつ値を返します。

```cpp
int plus_one(int &x) {
    x += 1;
    return x;
}

void print(int x, int y) {
    std::cout << x << "," << y << std::endl;
}

int main() {
    int a = 0;
    print(plus_one(a), a);
}
```

久しぶりにC++を書いていると書かなくても良い`mut`を書いたり`const`を書き忘れたりしちゃいますね。
それはさておき、このコードをそれぞれ `g++9.2.0` と `clang++9.0.0` でコンパイル&&実行すると、

```console:g++9.2.0
$ ./a.out
1,0
```

```console:clang++9.0.0
$ ./a.out
1,1
```

となってしまいます。どうやら**未規定動作**[^unspecified]を踏んでしまったらしく、
関数呼び出し`print(plus_one(), a)`の引数`plus_one()`と`a`の評価順序は決まっていないようです。

こういう挙動を知ってしまったからにはRustでの挙動を知りたくなるのがRustaceanの性です。

# 関数呼び出しの引数の評価順序

Rustで同様なコードを書くと以下のようになります。

```rust
fn plus_one(x: &mut i32) -> i32 {
    *x += 1;
    *x
}

fn print_values(x: i32, y: i32) {
    println!("{},{}", x, y);
}

fn main() {
    let mut a = 0;
    print_values(plus_one(&mut a), a);
}
```

これも`rustc`でコンパイル&&実行してみました。

```console
$ ./a
1,1
```
どうやら`clang`と同様に左から右へ評価されるようです :arrow_right: 

しかしC++と違い、Rustのコンパイラはほぼ`rustc`一択[^mrustc]なので、偶然このような実装になっているのか、評価順序が仕様で決められているのかどうか分かりません。確証を得たいところですが、残念ながら調べてみたところ[The Rust Reference](https://doc.rust-lang.org/stable/reference/)には記載されているところが見つかりませんでした[^doc_issue]。

# メソッド呼び出しでは？

関数呼び出しでは上記のような挙動を示します。それではメソッドの呼び出しではどうでしょうか？

```rust
struct A {
    value: i32,
}

impl A {
    fn new(value: i32) -> Self {
        Self { value }
    }

    fn print(self, value: i32) {
        println!("{},{}", self.value, value);
    }
}

fn main() {
    {println!("lhs"); A::new(0)}.print({println!("rhs"); 1});
}
```

```console
$ ./a
lhs
rhs
0,1
```

やはりここでも左から右へ実行されていますね。（間に挟まってる`print`の実行が最後じゃないか！という突っ込みは無しで :no_good: ）

# メソッド呼び出しは糖衣構文
Rustのメソッド呼び出しは幾つかの他の言語[^ufcs]と同様に一種の糖衣構文なので、この挙動は理解しやすいでしょう。

```rust
// 以下の二つのコードは同じ。
a.print(1);
A::print(a, 1);
```

上記の二つのコードで関数呼び出しとメソッド呼び出しは共に`a`→`1`の順に評価されてそうです。

さらっとRustのメソッド呼び出しは糖衣構文だと言って先のコードを示しましたが、実は上記のコードは上で定義した`A::print`に関してはおおかた正しいのですが、一般には正しくありません。

## 自動参照、自動参照外し

まず、Rustはメソッド呼び出しの際、**自動参照**および**参照外し**が行われます[^auto_ref]。
つまりメソッド宣言の第一引数は`self`、`&self`、`&mut self`のいずれかであるので、必要に応じて`*`、`&`、`&mut`を渡された引数につけて適切なものに変換します。ちなみに参照外しの`*`は値が`Copy`トレイトを実装している時のみコンパイル可能です。

```console:参照外し失敗の例
error[E0507]: cannot move out of `*b` which is behind a shared reference
  --> a.rs:20:5
   |
20 |     b.print(1);
   |     ^ move occurs because `*b` has type `A`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
```

コードにして書いてみると、`x.method(y)`は**必要があれば**以下のいずれかに変換されます。

```rust
(*x).method(y)     // 参照外し
(&x).method(y)     // 参照（不変）
(&mut x).method(y) // 参照（可変）
```

## 脱糖衣

自動参照、参照外しがなされた後は`X::method(x, y)`のような形に脱糖衣されます。
**が**、`method`が`&mut self`を受け取る時は以下のような特殊な挙動になります。

```rust
{
    let tmp1 = y;
    let tmp0 = &mut x;
    X::method(tmp0, tmp1)
}
```

メソッドの第一引数の評価（自動参照）は最後になっています。この挙動によって以下のようなコードがコンパイル可能になります。

```rust
let mut vec = vec![0];
vec.push(vec.len());
assert_eq!(vec, vec![0, 1]);
```

もしこのコードの２行目を`tmp0`や`tmp1`を用いずに脱糖衣するならば

```rust
Vec::push(&mut vec, vec.len());
```

のようになり、borrowチェッカーに怒られます :cry: 

```console
     Vec::push(&mut vec, vec.len());
     --------- --------  ^^^ immutable borrow occurs here
     |         |
     |         mutable borrow occurs here
     mutable borrow later used by call
```

ちなみに、`vec.push(vec.len())`のようなコードは比較的新しい`rustc`ではコンパイルできますが、`NLL`が導入される前の`rustc`には普通に怒られます。このような挙動ができるのは`NLL`導入時にライフタイムについて整備されたことによる寄与が大きいようです。`NLL`さまさまですね。

# さいごに

ここではあくまで、メソッド呼び出しはただの糖衣構文で脱糖衣されてコンパイルされるような書き方をしていますが、実際はエッジケースを回避すべくややこしい事を行っているようです[^rfc2025]。ここらについては興味本位で調べてみたはいいものの、実力不足でふわっとした理解しかできず、曖昧な記事を書くことになって申し訳ないです。
近いうちに`MIR`の勉強がてら、今回参考にした[RFC2025](https://github.com/rust-lang/rfcs/blob/master/text/2025-nested-method-calls.md)の翻訳をしつつ理解を深めれればなぁと思う次第です。


[^unspecified]: 当方C++完全理解者ではないので「未規定動作」については [こちらの記事](https://qiita.com/akinomyoga/items/592e5a3b8438a0c8556b#fnref980602) などをあたってください。
[^mrustc]: 他のRustコンパイラとしては「[mrustc](https://github.com/thepowersgang/mrustc)」がありますが試していません。
[^doc_issue]: The Rust Referenceに明記しようという [Issue](https://github.com/rust-lang/reference/issues/248) は立っていました。中の人たちもLTR(Left to Right)が妥当だという感じでした。
[^ufcs]: PythonとかD言語とか。D言語ではUFCSと呼ぶらしいですが、Rustの挙動はそれとは少し違います。D言語のUFCSについては [こちらの記事](https://qiita.com/nak2yoshi/items/1bbd91fb23d7cc67109e) を、RustはUFCSとは違うよねっていう話は [こちら](https://github.com/rust-lang/rfcs/issues/1140) を見てください。
[^auto_ref]: cf. [プログラミング言語Rust, 2nd Edition/メソッド記法](https://doc.rust-jp.rs/book/second-edition/ch05-03-method-syntax.html#a-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AF%E3%81%A9%E3%81%93%E3%81%AB%E8%A1%8C%E3%81%A3%E3%81%9F%E3%81%AE)
[^rfc2025]: cf. [RFC2025](https://github.com/rust-lang/rfcs/blob/master/text/2025-nested-method-calls.md)