Rustでgemを作ろう

B6a35c859bb72c6135f6c5d4a8bad402?s=47 atomiyama
December 14, 2019

 Rustでgemを作ろう

B6a35c859bb72c6135f6c5d4a8bad402?s=128

atomiyama

December 14, 2019
Tweet

Transcript

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

  2. whoami 冨⼭晶史(Tomiyama Akifumi) Birthday = Date.new(1991, 12, 16) # H3

    スタディプラス株式会社 Ruby 歴 2 年 Rust 歴 3 ヶ⽉ twitter: @atomiyama1216 2
  3. Rust とは https://www.rust-lang.org/ 3

  4. Rust の特徴 速度・安全性・並列性を重視した⾔語 所有権,参照と借⽤ コンパイラの強⼒なサポート ジェネリクスなどの型システム fn main() { let

    foo = String::from("foo"); let _baz = foo; println!("{}", foo); } //=> error[E0382]: borrow of moved value: `foo` 4
  5. 今⽇話すこと Rust で実装したコードをRuby から呼び出したい Ruby ⾼い柔軟性と書きやすさ Rust ⾼い安全性と速度 この2 つの⾔語の良いところをあわせてより良いア

    プリケーション開発をしたい 5
  6. Ruby からRust を呼び出す⽅法 拡張ライブラリ 公開されているC のAPI を使⽤する⽅法 -> mysql2, nokogiri

    e.g. rb_define_method FFI(Foreign Function Interface) 関数などのシグネチャをRuby から渡して呼び出す https://github.com/ffi/ffi 6
  7. 今回実装したもの 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
  8. 今回実装したもの Rust の関数 starts_with , ends_with を使って Symbol#start_with? , Symbol#end_with?

    を定義する https://doc.rust-lang.org/std/primitive.str.html#method.starts_with 8
  9. gem を作っていく 9

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

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

  12. gem プロジェクトの作成 > bundle gem rusty_symbol --ext rake-compiler rake compiler

    はC 拡張のビルドをサポート rust は未サポート Thermite Rust ベースのRuby 拡張のビルドを⽀援 12
  13. Thermite https://github.com/malept/thermite 13

  14. 2. ビルド周りの設定 14

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

  16. ビルド周りの設定 # 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
  17. ビルド周りの設定 # 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
  18. ビルド周りの設定 # 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
  19. 3. 実装 19

  20. Cargo でプロジェクトを作成 Rust で実装をするためにcargo でプロジェクトを作 成する # 生成 > cargo

    init --lib Created library package > git status -s M .gitignore ?? Cargo.toml ?? src/ 20
  21. 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
  22. 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
  23. 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
  24. 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
  25. 今回実装したいものは 25

  26. 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
  27. 実装ステップ 1. Ruby の⽂字列をRust のString に変換する 2. start_with? , end_with?

    を実装 3. Symbol クラスを拡張する https://doc.rust-lang.org/std/primitive.str.html#method.starts_with 27
  28. 1. Ruby の⽂字列をRust の⽂ 字列に変換する 28

  29. 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
  30. Symbol はちょっと違う 30

  31. 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
  32. シンボルをRust String に変換 1. VALUE をID に変換 2. ID からC

    の⽂字列ポインタを取得 3. C の⽂字列をRust のString に変換 32
  33. シンボルを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
  34. シンボルを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
  35. シンボルをRust String に変換 3. C の⽂字列をRust のString に変換 // src/lib.rs

    let rstr: String = unsafe { CStr::from_ptr(cstr).to_string_lossy().into_owned() }; 35
  36. 2. start_with? , end_with? を実装 36

  37. start_with? を実装 1. レシーバと引数を全てRust String に変換 2. 与えられた可変⻑の⽂字列にstarts_with を実⾏ 3.

    マッチしたらTrue ,なければFalse を返す 37
  38. 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
  39. 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
  40. 3. Symbol クラスを拡張する 40

  41. 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
  42. rb_define_method https://docs.ruby-lang.org/ja/latest/function/rb_define_method.html 42

  43. 関数シグネチャを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
  44. 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
  45. 実⾏してみる > 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
  46. まとめ 46

  47. まとめ thermite を使えば簡単にビルドできる Ruby はVALUE( ⼀部を除く) Symbol はID Ruby C

    API すごい Rust めっちゃ⾯⽩い 47
  48. おまけ 48

  49. 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
  50. 全然早くない > 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
  51. 参考⽂献 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

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

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