「実践Rustプログラミング入門」に入門する(4日目)RubyからRustを呼ぶ

# 概要
[「実践Rustプログラミング入門」](https://www.shuwasystem.co.jp/book/9784798061702.html)pp.421-427を参照して、RubyからRustの関数を呼ぶ。
そういえば今日(2020年9月4日(金))は[Ruby Kaigi](https://rubykaigi.org/2020-takeout)。

# Windows用の準備
[RubyInstaller for Windows](https://rubyinstaller.org/downloads/)から、Windows用のRubyをダウンロードする。
本記事執筆時点最新は[Ruby+Devkit 2.7.1-1 (x64)](https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-2.7.1-1/rubyinstaller-devkit-2.7.1-1-x64.exe)。

![01_RubyInstaller2_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2059/e2e7c7ec-f3c5-1952-72c2-73dfbf9004b6.png)

"If unsure press ENTER"とあるのでENTERで進む。

![02_RubyInstaller2_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2059/afb35541-8372-3b85-986c-ba2e62bcf0cf.png)

"Install MSYS2 and MINGW developent toolchain succeeded"とのことなので、ENTERは、3を押したのと同じことだったか。
ここでENTERを押して、インストール終了。

Windows PowerShell上でrubyの動作を確認する。

```shell
PS > ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x64-mingw32]
```

VSCodeでもTERMINALを開きなおせば確認できる。

# p.421 サンプル:RubyからRustの関数を呼ぶ。

p.421の実験を行うためのプロジェクトディレクトリを作る。

```shell
PS > cargo new ffitest
     Created binary (application) `ffitest` package
```

ffitestの下にsampleディレクトリを作り、Rubyのコードを書いて、Rubyの処理を実験する。

```ruby:ffitest/sample/add_array.rb
def add_array(n,x)
    a = Array.new(n,0)
    x.times do
        for i in 0..x-1
            a[i] += 1
        end
    end
    a.sum
end

puts add_array(ARGV[0].to_i, ARGV[1].to_i)
```

[Measure-Command](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/measure-command?view=powershell-7)は、Linuxでの`time`相当のコマンド。

```shell
PS ffitest\sample> Measure-Command {ruby add_array.rb 10000 10000}       


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 8
Milliseconds      : 53
Ticks             : 80530169
TotalDays         : 9.32062141203704E-05
TotalHours        : 0.00223694913888889
TotalMinutes      : 0.134216948333333
TotalSeconds      : 8.0530169
TotalMilliseconds : 8053.0169
```

TotalSecondsから、約8秒かかっていることがわかる。

つづいてRust側。src/main.rsをsrc/add_array.rsにRenameする。

```rust:src/add_array.rs
fn add_array(n: u64, x: u64) -> u64 {
    let mut a = vec![0u64; n as usize];
    for _ in 0..x {
        for i in 0..n as usize {
            a[i] += 1;
        }
    }
    a.iter().sum()
}

use std::env;
fn main() {
    let args: Vec<_> = env::args().collect();
    let n = args[1].parse::<u64>().unwrap();
    let x = args[2].parse::<u64>().unwrap();
    println!("{}", add_array(n, x));
}
```

Cargo.tomlに、add_arrayの実行設定を追記する。

```Cargo.toml
[[bin]]
name = "add_array"
path = "src/add_array.rs"
```

p.422最下段のビルド&実行確認を行う。

```shell
PS ffitest> cargo build --release
   Compiling ffitest v0.1.0 (ffitest)
    Finished release [optimized] target(s) in 1.29s     
PS ffitest> Measure-Command {./target/release/add_array 10000 10000}     


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 93
Ticks             : 935455
TotalDays         : 1.0827025462963E-06
TotalHours        : 2.59848611111111E-05
TotalMinutes      : 0.00155909166666667
TotalSeconds      : 0.0935455
TotalMilliseconds : 93.5455
```

0.09秒。さすがに速い。

# p.423 ライブラリ化

ライブラリのプロジェクトディレクトリを作成する。

```shell
PS > cargo new --lib addarray
     Created library `addarray` package
```

Cargo.tomlに[動的ライブラリ指定](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/cdylib-crates-for-c-interoperability.html)を行う。

```addarray/Cargo.toml
[lib]
crate-type = ["cdylib"]
```

ここまでの作業で、src/lib.rsが作成されている。中身は以下の通り。

```rust:src/lib.rs
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
```

上記のコードは消去し、p.424の2つめのlib.rs通りに記述する。

```rust:src/lib.rs
#[no_mangle]
pub extern "C" fn add_array(n: u64, x: u64) -> u64 {
    let mut a = vec![0u64; n as usize];
    for _ in 0..x {
        for i in 0..n as usize {
            a[i] += 1;
        }
    }
    a.iter().sum()
}
```

ビルドする。いろいろ思考錯誤した結果、`--target=x86_64-pc-windows-msvc`とすると、今回のRubyで動かせるDLLになるようだ(調査不足)。

```shell
PS > cargo build --release --target=x86_64-pc-windows-msvc       
   Compiling addarray v0.1.0 (addarray)
    Finished release [optimized] target(s) in 0.87s   
```

出来たライブラリを確認する。

```shell
PS addarray> ls .\target\x86_64-pc-windows-msvc\release\


    ディレクトリ: addarray\target\x86_64-pc-windows-msvc\release


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2020/09/04     --:--                .fingerprint
d-----       2020/09/04     --:--                build
d-----       2020/09/04     --:--                deps
d-----       2020/09/04     --:--                examples
d-----       2020/09/04     --:--                incremental
-a----       2020/09/04     --:--              0 .cargo-lock
-a----       2020/09/04     --:--            109 addarray.d
-a----       2020/09/04     --:--         128512 addarray.dll
-a----       2020/09/04     --:--            980 addarray.dll.exp
-a----       2020/09/04     --:--           1942 addarray.dll.lib
-a----       2020/09/04     --:--         937984 addarray.pdb
```

addarray.dllができていればOK。

# p.425 Rubyからの呼び出し

Rubyのffiをインストールする。

```shell
PS > gem install ffi
Fetching ffi-1.13.1-x64-mingw32.gem
Successfully installed ffi-1.13.1-x64-mingw32
Parsing documentation for ffi-1.13.1-x64-mingw32
Installing ri documentation for ffi-1.13.1-x64-mingw32
Done installing documentation for ffi after 1 seconds
1 gem installed
```

Rubyのソースコードは、addarray/sample/add_array_rs.rbとして作成する。DLLのパス指定に注意。相対パス指定していたらハマったので、絶対パス指定にした。

```ruby:addarray/sample/add_array_rs.rb
require 'ffi'

module AddArray
    extend FFI::Library
    ffi_lib 'C:\your\path\addarray\target\x86_64-pc-windows-msvc\release\addarray.dll'
    attach_function :add_array, [:uint64, :uint64], :uint64
end

puts AddArray::add_array(ARGV[0].to_i, ARGV[1].to_i)
```

# p.426 実行確認

```shell
PS addarray\sample> Measure-Command {ruby .\add_array_rs.rb 10000 10000} 


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 311
Ticks             : 3111198
TotalDays         : 3.60092361111111E-06
TotalHours        : 8.64221666666667E-05
TotalMinutes      : 0.00518533
TotalSeconds      : 0.3111198
TotalMilliseconds : 311.1198
```

0.3秒。0.09秒からすると遅くはなるが、もともと8秒だったのだから素晴らしい改善。

# 番外編:Windows用Ruby&Rust連携のための環境設定

Windows上で動作させる場合は、Rubyの環境に合わせてRust側が適切なDLLを作成しなければならない。という落とし穴にハマり、調査した雑記。

MINGW64のtargetを追加する場合。

```shell
PS > rustup target add x86_64-pc-windows-gnu
info: downloading component 'rust-std' for 'x86_64-pc-windows-gnu'
info: installing component 'rust-std' for 'x86_64-pc-windows-gnu'
info: Defaulting to 500.0 MiB unpack ram
 14.1 MiB /  14.1 MiB (100 %)  10.9 MiB/s in  1s ETA:  0s
```

rustupを使って、targetの一覧を確認する。

```shell
PS addarray> rustup show
Default host: x86_64-pc-windows-msvc
rustup home:  .rustup

installed targets for active toolchain
--------------------------------------

i686-pc-windows-gnu
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc

active toolchain
----------------

stable-x86_64-pc-windows-msvc (default)
rustc 1.45.2 (d3fb005a3 2020-07-31)
```

`cargo build`すると、「x86_64-w64-mingw32-gccがみつからない」と怒られるケースがあった。PATHを追加する。

```shell
PS > $ENV:Path="C:\Ruby27-x64\msys64\mingw64\bin;"+$ENV:Path
```

ビルドする。

```shell
PS addarray> cargo build --release --target=x86_64-pc-windows-gnu --verbose
       Fresh addarray v0.1.0 (addarray)
    Finished release [optimized] target(s) in 0.02s
```

addarray.dllができている。

```shell
PS addarray> ls .\target\x86_64-pc-windows-gnu\release


    ディレクトリ: addarray\target\x86_64-pc-windows-gnu\release


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2020/09/04     --:--                .fingerprint
d-----       2020/09/04     --:--                build
d-----       2020/09/04     --:--                deps
d-----       2020/09/04     --:--                examples
d-----       2020/09/04     --:--                incremental
-a----       2020/09/04     --:--              0 .cargo-lock
-a----       2020/09/04     --:--            108 addarray.d
-a----       2020/09/04     --:--        3689956 addarray.dll
-a----       2020/09/04     --:--           2056 libaddarray.dll.a
```

## add_array_rs.rb修正&動作確認

改めて、Rubyコード内のDLL指定を変更する。

```ruby:add_array_rs.rb
require 'ffi'

module AddArray
    extend FFI::Library
    ffi_lib 'C:\your\path\addarray\target\x86_64-pc-windows-gnu\release\addarray.dll'
    attach_function :add_array, [:uint64, :uint64], :uint64
end

puts AddArray::add_array(ARGV[0].to_i, ARGV[1].to_i)
```

# 反省
1. Windowsで実験するときは、実行環境に注意する。mingwや32ビット、64ビットなど、いろいろと落とし穴がある。