Get started with "Introduction to Practical Rust Programming" (Day 4) Call Rust from Ruby

Overview

"Introduction to Practical Rust Programming" Refer to pp.421-427 and call Rust functions from Ruby. By the way, today (Friday, September 4, 2020) is Ruby Kaigi.

Preparation for Windows

Download Ruby for Windows from RubyInstaller for Windows. At the time of writing this article, the latest is [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

There is "If unsure press ENTER", so press ENTER to proceed.

02_RubyInstaller2_02.png

Since it says "Install MSYS2 and MINGW developent toolchain succeeded", was ENTER the same as pressing 3? Now press ENTER to complete the installation.

Check the operation of ruby on Windows PowerShell.

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

You can check it with VS Code by reopening TERMINAL.

p.421 Sample: Call a Rust function from Ruby.

Create a project directory for the experiment on p.421.

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

Create a sample directory under ffitest, write Ruby code, and experiment with Ruby processing.

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 is equivalent to time on Linux command.

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

From TotalSeconds, you can see that it takes about 8 seconds.

Next is the Rust side. Rename src / main.rs to src / add_array.rs.

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));
}

Add the execution setting of add_array to Cargo.toml.

Cargo.toml


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

p.422 Perform build & execution confirmation at the bottom.

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 seconds. It's really fast.

p.423 Library

Create a project directory for the library.

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

[Dynamic library specification] in Cargo.toml (https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/cdylib-crates-for-c-interoperability.html )I do.

addarray/Cargo.toml


[lib]
crate-type = ["cdylib"]

By the work so far, src / lib.rs has been created. The contents are as follows.

src/lib.rs


#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

Delete the above code and write it according to the second lib.rs on p.424.

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()
}

Build. As a result of various thoughts and mistakes, if --target = x86_64-pc-windows-msvc is set, it seems that it will be a DLL that can be run with Ruby this time (insufficient research).

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

Check the created library.

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


directory: 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

OK if addarray.dll is created.

p.425 Call from Ruby

Install Ruby ffi.

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

The Ruby source code is created as addarray / sample / add_array_rs.rb. Be careful when specifying the DLL path. I was addicted to specifying a relative path, so I specified an absolute path.

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 Execution confirmation

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 seconds. It's slower than 0.09 seconds, but it was originally 8 seconds, so it's a great improvement.

Extra: Environment settings for Ruby & Rust integration for Windows

When running on Windows, Rust must create an appropriate DLL according to the Ruby environment. I was addicted to the pitfall and investigated the miscellaneous notes.

When adding a MINGW64 target.

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

Use rustup to see the list of targets.

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)

When I did cargo build, I was angry that I couldn't find x86_64-w64-mingw32-gcc. Add PATH.

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

Build.

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 is created.

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


directory: 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 Modification & operation check

Change the DLL specification in the Ruby code again.

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)

Reflection

  1. When experimenting with Windows, pay attention to the execution environment. There are various pitfalls such as mingw, 32-bit, 64-bit, etc.

Recommended Posts

Get started with "Introduction to Practical Rust Programming" (Day 4) Call Rust from Ruby
Get started with "Introduction to Practical Rust Programming" (Day 3)
Introduction to Practical Programming
Let's get started with parallel programming
How to get started with slim
Introduction to Ruby (from other languages)
Introduction to Ruby basic grammar with yakiniku
I tried to get started with WebAssembly
[Note] How to get started with Rspec
Start with a browser Ruby game programming: Introduction to Nyle-canvas (DXRuby style)
Sample to create PDF from Excel with Ruby
Introduction to Ruby 2
How to get started with Eclipse Micro Profile
Try to get redmine API key with ruby
Rails beginners tried to get started with RSpec
Introduction to programming for college students (updated from time to time)
I tried to get started with Spring Data JPA
How to get started with creating a Rails app
How to get jdk etc from oracle with cli
Getting Started with Ruby
From Java to Ruby !!
Get started with Gradle
How to get started with Gatsby (TypeScript) x Netlify x Docker
Now is the time to get started with the Stream API
How to get started with JDBC using PostgresSQL on MacOS
I tried to get started with Swagger using Spring Boot
I tried to create an API to get data from a spreadsheet in Ruby (with service account)
Get started with Spring boot
Get started with DynamoDB with docker
[Ruby] Introduction to Ruby Error statement
Getting Started with Ruby Modules
[ruby] Method call with argument
Easy code review to get started with Jenkins / SonarQube: Static analysis
How to get and add data from Firebase Firestore in Ruby