Slide 1

Slide 1 text

Rust でGem を作ろう Akifumi Tomiyama スタディプラス株式会社 平成Ruby 会議01 1

Slide 2

Slide 2 text

whoami 冨⼭晶史(Tomiyama Akifumi) Birthday = Date.new(1991, 12, 16) # H3 スタディプラス株式会社 Ruby 歴 2 年 Rust 歴 3 ヶ⽉ twitter: @atomiyama1216 2

Slide 3

Slide 3 text

Rust とは https://www.rust-lang.org/ 3

Slide 4

Slide 4 text

Rust の特徴 速度・安全性・並列性を重視した⾔語 所有権,参照と借⽤ コンパイラの強⼒なサポート ジェネリクスなどの型システム fn main() { let foo = String::from("foo"); let _baz = foo; println!("{}", foo); } //=> error[E0382]: borrow of moved value: `foo` 4

Slide 5

Slide 5 text

今⽇話すこと Rust で実装したコードをRuby から呼び出したい Ruby ⾼い柔軟性と書きやすさ Rust ⾼い安全性と速度 この2 つの⾔語の良いところをあわせてより良いア プリケーション開発をしたい 5

Slide 6

Slide 6 text

Ruby からRust を呼び出す⽅法 拡張ライブラリ 公開されているC のAPI を使⽤する⽅法 -> mysql2, nokogiri e.g. rb_define_method FFI(Foreign Function Interface) 関数などのシグネチャをRuby から渡して呼び出す https://github.com/ffi/ffi 6

Slide 7

Slide 7 text

今回実装したもの String#start_with? , end_with? と同等のものを Symbol クラスに拡張するgem を作成する :some_symbol.start_with?(:some) #=> true :some_symbol.start_with?(:symbol) #=> false :some_symbol.end_with?(:some) #=> false :some_symbol.end_with?(:symbol) #=> true https://github.com/atomiyama/rusty_symbol 7

Slide 8

Slide 8 text

今回実装したもの Rust の関数 starts_with , ends_with を使って Symbol#start_with? , Symbol#end_with? を定義する https://doc.rust-lang.org/std/primitive.str.html#method.starts_with 8

Slide 9

Slide 9 text

gem を作っていく 9

Slide 10

Slide 10 text

gem を作るには 1. gem プロジェクトの作成 2. ビルド周りの設定 3. 実装 4. 公開 10

Slide 11

Slide 11 text

1. gem プロジェクトの作成 11

Slide 12

Slide 12 text

gem プロジェクトの作成 > bundle gem rusty_symbol --ext rake-compiler rake compiler はC 拡張のビルドをサポート rust は未サポート Thermite Rust ベースのRuby 拡張のビルドを⽀援 12

Slide 13

Slide 13 text

Thermite https://github.com/malept/thermite 13

Slide 14

Slide 14 text

2. ビルド周りの設定 14

Slide 15

Slide 15 text

ビルド周りの設定 @sinsoku_listy さんの発表を参考にしました https://speakerdeck.com/sinsoku/how-to-make-a-gem-with-rust 15

Slide 16

Slide 16 text

ビルド周りの設定 # rusty_symbol.gemspec Gem::Specification.new do |spec| spec.name = "rusty_symbol" ... spec.require_paths = ["lib"] spec.extensions = ['ext/rusty_symbol/extconf.rb'] spec.add_dependency "thermite" spec.add_development_dependency "bundler", "~> 1.17" spec.add_development_dependency "rake", "~> 10.0" ... end 16

Slide 17

Slide 17 text

ビルド周りの設定 # ext/build.rb require 'thermite/tasks' Thermite::Config.prepend( Module.new do def shared_library @shared_library ||= "#{library_name}.#{RbConfig::CONFIG["DLEXT"] || 'so'}" end def ruby_extension_dir @ruby_extension_dir ||= @options.fetch(:ruby_extension_dir, 'lib') end def ruby_extension_path ruby_path(ruby_extension_dir, shared_library) end end ) project_dir = File.dirname(File.dirname(__FILE__)) Thermite::Tasks.new(cargo_project_path: project_dir, ruby_project_path: project_dir, ruby_extension_dir: "lib/rusty_symbol") 17

Slide 18

Slide 18 text

ビルド周りの設定 # Rakefile require "bundler/gem_tasks" require "rspec/core/rake_task" require_relative "ext/build" RSpec::Core::RakeTask.new(:spec) require "rake/extensiontask" task build: "thermite:build" Rake::ExtensionTask.new("rusty_symbol") do |ext| ext.lib_dir = "lib/rusty_symbol" end task :default => [:clobber, "thermite:build", :spec] 18

Slide 19

Slide 19 text

3. 実装 19

Slide 20

Slide 20 text

Cargo でプロジェクトを作成 Rust で実装をするためにcargo でプロジェクトを作 成する # 生成 > cargo init --lib Created library package > git status -s M .gitignore ?? Cargo.toml ?? src/ 20

Slide 21

Slide 21 text

Cargo.toml の設定 libc はRust にC の型定義を提供してくれます. .gemspec に似てる [package] name = "rusty_symbol" version = "0.1.0" authors = ["atomiyama <*****@gmail.com>"] edition = "2018" [lib] path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] libc = "*" 21

Slide 22

Slide 22 text

Hello world rake build を実⾏すると lib/rusty_symbol/rusty_symbol.bundle ができる. › rake build checking for cargo... yes ... Compiling rusty_symbol v0.1.0 (/path/to/rusty_symbol) Finished release [optimized] target(s) in 1.36s rusty_symbol 0.1.0 built to pkg/rusty_symbol-0.1.0.gem. 22

Slide 23

Slide 23 text

Hello world require された時 lib/rusty_symbol/rusty_symbol.bundle の Init_rusty_symbol が呼び出される. # lib/rusty_symbol.rb require "rusty_symbol/rusty_symbol" // src/lib.rs #[no_mangle] pub extern "C" fn Init_rusty_symbol() { println!("hello 平成Ruby会議01"); } 23

Slide 24

Slide 24 text

Hello world # > rake build # .bundle 生成 > ls lib/rusty_symbol/rusty_symbol.bundle lib/rusty_symbol/rusty_symbol.bundle # 実行 > bin/console hello 平成Ruby会議01 [1] pry(main)> branch: hello_world 24

Slide 25

Slide 25 text

今回実装したいものは 25

Slide 26

Slide 26 text

Symbol#start_with? String#start_with? ( end_with? ) と同等の挙動をす るものをSymbol クラスに移植 :some_symbol.start_with?(:some) #=> true :some_symbol.start_with?(:symbol) #=> false :some_symbol.end_with?(:some) #=> false :some_symbol.end_with?(:symbol) #=> true 26

Slide 27

Slide 27 text

実装ステップ 1. Ruby の⽂字列をRust のString に変換する 2. start_with? , end_with? を実装 3. Symbol クラスを拡張する https://doc.rust-lang.org/std/primitive.str.html#method.starts_with 27

Slide 28

Slide 28 text

1. Ruby の⽂字列をRust の⽂ 字列に変換する 28

Slide 29

Slide 29 text

Ruby Object はVALUE で構造体 // ruby.h // https://github.com/ruby/ruby/blob/v2_6_5/include/ruby/ruby.h#L94-L115 typedef unsigned long VALUE; 29

Slide 30

Slide 30 text

Symbol はちょっと違う 30

Slide 31

Slide 31 text

Symbol はID Symbol object のVALUE は構造体を指すポイン タではない このVALUE はID 型の整数値 ID からC のchar が取り出せる シンボルは⽂字列の⽪を被った整数値 // https://github.com/ruby/ruby/blob/master/include/ruby/ruby.h#L103 typedef unsigned long ID; http://i.loveruby.net/ja/rhg/book/object.html 31

Slide 32

Slide 32 text

シンボルをRust String に変換 1. VALUE をID に変換 2. ID からC の⽂字列ポインタを取得 3. C の⽂字列をRust のString に変換 32

Slide 33

Slide 33 text

シンボルをRust String に変換 1. VALUE をID に変換 // https://github.com/ruby/ruby/blob/master/symbol.c#L747-L772 ID rb_sym2id(VALUE sym) { ... } // src/lib.rs let id: ID = unsafe { rb_sym2id(rb_self) }; 33

Slide 34

Slide 34 text

シンボルをRust String に変換 2. ID からC の⽂字列を取得 // https://github.com/ruby/ruby/blob/master/symbol.c#L800-L813 const char * rb_id2name(ID id) { ... } // src/lib.rs let cstr: *const c_char = unsafe { rb_id2name(id) }; 34

Slide 35

Slide 35 text

シンボルをRust String に変換 3. C の⽂字列をRust のString に変換 // src/lib.rs let rstr: String = unsafe { CStr::from_ptr(cstr).to_string_lossy().into_owned() }; 35

Slide 36

Slide 36 text

2. start_with? , end_with? を実装 36

Slide 37

Slide 37 text

start_with? を実装 1. レシーバと引数を全てRust String に変換 2. 与えられた可変⻑の⽂字列にstarts_with を実⾏ 3. マッチしたらTrue ,なければFalse を返す 37

Slide 38

Slide 38 text

start_with? extern fn rb_sym_start_with(argc: c_int, argv: *const VALUE, rb_self: VALUE) -> VALUE { // if no arguments return false if argc == 0 { return Boolean::False as VALUE }; // parse variable arguments into vec. let argv = unsafe { slice::from_raw_parts(argv, argc as usize).to_vec() }; // transform Symbol into String let id: ID = unsafe { rb_sym2id(rb_self) }; let cstr: *const c_char = unsafe { rb_id2name(id) }; let rstr: String = unsafe { CStr::from_ptr(cstr).to_string_lossy().into_owned() }; for arg in argv { let id: ID = unsafe { rb_sym2id(arg) }; let arg_cstr: *const c_char = unsafe { rb_id2name(id) }; let arg_str: String = unsafe { CStr::from_ptr(arg_cstr).to_string_lossy().into_owned() }; if rstr.starts_with(&arg_str) { return Boolean::True as VALUE } }; Boolean::False as VALUE } 38

Slide 39

Slide 39 text

end_with? extern fn rb_sym_end_with(argc: c_int, argv: *const VALUE, rb_self: VALUE) -> VALUE { // if no arguments return false if argc == 0 { return Boolean::False as VALUE }; // parse variable arguments into vec. let argv = unsafe { slice::from_raw_parts(argv, argc as usize).to_vec() }; // transform Symbol into String let id: ID = unsafe { rb_sym2id(rb_self) }; let cstr: *const c_char = unsafe { rb_id2name(id) }; let rstr: String = unsafe { CStr::from_ptr(cstr).to_string_lossy().into_owned() }; for arg in argv { let id: ID = unsafe { rb_sym2id(arg) }; let arg_cstr: *const c_char = unsafe { rb_id2name(id) }; let arg_str: String = unsafe { CStr::from_ptr(arg_cstr).to_string_lossy().into_owned() }; if rstr.ends_with(&arg_str) { return Boolean::True as VALUE } }; Boolean::False as VALUE } 39

Slide 40

Slide 40 text

3. Symbol クラスを拡張する 40

Slide 41

Slide 41 text

Symbol クラスをRust で定義 // string.c rb_cSymbol = rb_define_class("Symbol", rb_cObject); // src/lib.rs extern crate libc; use libc::{ c_ulong }; type VALUE = c_ulong; extern { static rb_cSymbol: VALUE; } https://github.com/ruby/ruby/blob/master/string.c#L11369 41

Slide 42

Slide 42 text

rb_define_method https://docs.ruby-lang.org/ja/latest/function/rb_define_method.html 42

Slide 43

Slide 43 text

関数シグネチャをRust で定義 rb_define_method を定義する extern crate libc; use libc::{ c_ulong, c_int, c_char }; type VALUE = c_ulong; type c_func = *const void; extern { fn rb_define_method(klass: VALUE, name: *const c_char, func: c_func, argc: c_int); } 43

Slide 44

Slide 44 text

Symbol クラスを拡張する #[allow(non_snake_case)] #[no_mangle] pub extern "C" fn Init_rusty_symbol() { let sym_start_with = CString::new("start_with?").unwrap(); let sym_end_with = CString::new("end_with?").unwrap(); unsafe { rb_define_method(rb_cSymbol, sym_start_with.as_ptr(), rb_sym_start_with as c_func, -1); rb_define_method(rb_cSymbol, sym_end_with.as_ptr(), rb_sym_end_with as c_func, -1); } } 44

Slide 45

Slide 45 text

実⾏してみる > rake build checking for cargo... yes ... Compiling rusty_symbol v0.1.0 (/path/to/rusty_symbol) Finished release [optimized] target(s) in 1.54s rusty_symbol 0.1.0 built to pkg/rusty_symbol-0.1.0.gem. > bin/console [1] pry(main)> :heiseirubykaigi.start_with?(:heisei) => true [2] pry(main)> :heiseirubykaigi.start_with?(:reiwa) => false [3] pry(main)> 45

Slide 46

Slide 46 text

まとめ 46

Slide 47

Slide 47 text

まとめ thermite を使えば簡単にビルドできる Ruby はVALUE( ⼀部を除く) Symbol はID Ruby C API すごい Rust めっちゃ⾯⽩い 47

Slide 48

Slide 48 text

おまけ 48

Slide 49

Slide 49 text

require "rusty_symbol" require "benchmark/ips" class Symbol def _start_with?(*argv) self.to_s.start_with?(*argv.map(&:to_s)) end def _end_with?(*argv) self.to_s.end_with?(*argv.map(&:to_s)) end end Benchmark.ips do |x| x.report "Ruby" do 1_000_000.times do :some_symbol._start_with?(:foo, :baz, :bar, :some) end end x.report "Rust" do 1_000_000.times do :some_symbol.start_with?(:foo, :baz, :bar, :symbol) end end x.compare! end 49

Slide 50

Slide 50 text

全然早くない > bundle exec ruby benchmark.rb Warming up -------------------------------------- Ruby 1.000 i/100ms Rust 1.000 i/100ms Calculating ------------------------------------- Ruby 1.391 (± 0.0%) i/s - 7.000 in 5.035024s Rust 1.418 (± 0.0%) i/s - 8.000 in 5.648497s Comparison: Rust: 1.4 i/s Ruby: 1.4 i/s - 1.02x slower 50

Slide 51

Slide 51 text

参考⽂献 https://github.com/ruby/ruby https://speakerdeck.com/sinsoku/how-to- make-a-gem-with-rust http://i.loveruby.net/ja/rhg/book/object.html https://docs.ruby- lang.org/ja/latest/function/index.html 51

Slide 52

Slide 52 text

Special Thanks 平成Ruby 会議01 運営のみなさま スライド作成に協⼒してくれた⽅々 Kotaro Ambai(@bai2_25) Studyplus の同僚の⽅々 52

Slide 53

Slide 53 text

ご静聴ありがとうございました 53