Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to make a gem with Rust

How to make a gem with Rust

Ecad9d801d79f6c6e5df93094690685e?s=128

Takumi Shotoku

November 30, 2019
Tweet

More Decks by Takumi Shotoku

Other Decks in Programming

Transcript

  1. How to make a gem with Rust 鹿児島Ruby会議01 2019/11/30(Sat) 1

  2. 自己紹介 名前: 神速 会社: メドピア株式会社 出身: 鹿児島 ! GitHub: @sinsoku

    (アイコン右上) Twitter: @sinsoku_listy (アイコン右下) Ruby/Rails歴: 約7年 ⭐ Rust歴: 約3ヶ月 # 2
  3. Rubyは良い言語です 3

  4. しかし、いくつか弱点があります • ! 実行速度が遅い • " 型がない • # 実行時エラーが起きやすい

    • $ 少し飽きてきた 4
  5. そんなときRustに出会った1 1 引用: 王様達のヴァイキング 11巻 5

  6. 公式ページに記載されてる特徴2 2 https://www.rust-lang.org/ 6

  7. Rustの特徴 • ! 実行速度が速い • " 型、メモリ安全、スレッド安全 • # 分かりやすいエラーを備えたコンパイラ

    • ✨ ジェネリクス, パターンマッチ, Optional, ...etc モダンな機能をだいたい持っている 7
  8. 両方の良さを活かしたい • Rustを使えるエンジニアは少ない • Rustの求人はとても少ない • Ruby/Railsの良さは手放したくない • "Rubyを通して越境する" というテーマ

    メインはRails、一部にRustを使いたい 8
  9. malept/thermite 9

  10. テルミット法3 3 引用: https://ja.wikipedia.org/wiki/テルミット法 10

  11. 拡張ライブラリ? 11

  12. 拡張ライブラリとは • gem install のときにビルドするやつ • nokogiriやmysql2など • 処理の一部がC言語で書かれている •

    Rubyの内部メソッドを使える • メタプロ以上にヤバいことができる • SEGV... " 12
  13. Exploring Internal Ruby Through C Extensions4 4 https://speakerdeck.com/yuryu/exploring-internal-ruby-through-c-extensions 13

  14. bundle gem wasabi --ext Creating gem 'wasabi'... MIT License enabled

    in config Code of conduct enabled in config create wasabi/Gemfile create wasabi/lib/wasabi.rb create wasabi/lib/wasabi/version.rb create wasabi/wasabi.gemspec create wasabi/Rakefile create wasabi/README.md create wasabi/bin/console create wasabi/bin/setup create wasabi/.gitignore create wasabi/.travis.yml create wasabi/.rspec create wasabi/spec/spec_helper.rb create wasabi/spec/wasabi_spec.rb create wasabi/LICENSE.txt create wasabi/CODE_OF_CONDUCT.md create wasabi/ext/wasabi/extconf.rb create wasabi/ext/wasabi/wasabi.h create wasabi/ext/wasabi/wasabi.c Initializing git repo in /Users/sinsoku/.ghq/github.com/sinsoku/wasabi Gem 'wasabi' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html 14
  15. ext/wasabi/wasabi.c #include "wasabi.h" VALUE rb_mWasabi; void Init_wasabi(void) { rb_mWasabi =

    rb_define_module("Wasabi"); } 15
  16. wasabi.gemspec Gem::Specification.new do |spec| spec.extensions = ["ext/wasabi/extconf.rb"] ext/wasabi/extconf.rb require "mkmf"

    create_makefile("wasabi/wasabi") 16
  17. rake build mkdir -p tmp/x86_64-darwin18/wasabi/2.7.0 cd tmp/x86_64-darwin18/wasabi/2.7.0 /Users/sinsoku/.rbenv/versions/2.7.0-preview1/bin/ruby -I. ../../../../ext/wasabi/extconf.rb

    creating Makefile cd - cd tmp/x86_64-darwin18/wasabi/2.7.0 /usr/bin/make compiling ../../../../ext/wasabi/wasabi.c linking shared-object wasabi/wasabi.bundle cd - mkdir -p tmp/x86_64-darwin18/stage/lib/wasabi install -c tmp/x86_64-darwin18/wasabi/2.7.0/wasabi.bundle lib/wasabi/wasabi.bundle cp tmp/x86_64-darwin18/wasabi/2.7.0/wasabi.bundle tmp/x86_64-darwin18/stage/lib/wasabi/wasabi.bundle wasabi 0.1.0 built to pkg/wasabi-0.1.0.gem. lib/wasabi/wasabi.bundle が作られる。 17
  18. 拡張ライブラリの要約 • rake build すると lib/wasabi/wasabi.bundle ができる • reuqire "wasabi/wasabi"で拡張ライブラリ

    (*.so,*.o,*.dll など) を読み込める • 読み込んだときにC言語で定義した Init_xxx が呼ばれる 18
  19. Thermite 19

  20. Thermite v0.13.0には以下の問題がある • Macなのに*.soの拡張子が作られてしまう • 拡張ライブラリの出力先を変更できない • デフォルトだと lib/wasabi.bundle になる

    • masterでは変更できる 20
  21. モンキーパッチを当てて直す # 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) root_path = File.expand_path('..', __dir__) Thermite::Tasks.new( cargo_project_path: root_path, ruby_project_path: root_path, ruby_extension_dir: 'lib/wasabi' ) 21
  22. git diff -- Rakefile diff --git a/Rakefile b/Rakefile index e81a493..26e25a2

    100644 --- a/Rakefile +++ b/Rakefile @@ -1,14 +1,15 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require_relative 'ext/build' RSpec::Core::RakeTask.new(:spec) require "rake/extensiontask" -task :build => :compile +task :build => 'thermite:build' Rake::ExtensionTask.new("wasabi") do |ext| ext.lib_dir = "lib/wasabi" end -task :default => [:clobber, :compile, :spec] +task :default => [:clobber, 'thermite:build', :spec] 22
  23. git diff -- ext/Rakefile diff --git a/ext/Rakefile b/ext/Rakefile new file

    mode 100644 index 0000000..2187642 --- /dev/null +++ b/ext/Rakefile @@ -0,0 +1,2 @@ +require_relative 'build' +task default: %w(thermite:build) 23
  24. Rustでコードを書く 24

  25. cargoで雛形を作る $ cargo init --lib Created library package $ git

    status --short M .gitignore ?? Cargo.toml ?? src/ 25
  26. Cargo.tomlを更新 [lib] crate-type = ["cdylib"] [dependencies] ruru = { git

    = "https://github.com/d-unseductable/ruru" } ruby-sys = { git = "https://github.com/steveklabnik/ruby-sys" } [replace] "ruby-sys:0.3.0" = { git = "https://github.com/steveklabnik/ruby-sys" } 26
  27. ruby-sys / ruru • ruby-sys • Rubyの低レベル関数のバインディング • rb_define_module など

    • ruru • ruby-sysより高レベルなAPIを提供する • 内部でruby-sysを使っている 27
  28. Rustでコードを書く use ruby_sys::{class, fixnum, util}; use ruby_sys::types::{Value, SignedValue, CallbackPtr}; use

    ruru::util::str_to_cstring; use std::ptr; extern fn rb_sum(_mod: Value, a :Value, b: Value) -> Value { let a = unsafe { fixnum::rb_num2int(a) as i64 }; let b = unsafe { fixnum::rb_num2int(b) as i64 }; let sum = a + b; unsafe { fixnum::rb_int2inum(sum as SignedValue) } } extern fn rb_call_to_s(_mod: Value, obj: Value) -> Value { unsafe { let method_id = util::rb_intern(str_to_cstring("to_s").as_ptr()); util::rb_funcallv(obj, method_id, 0, ptr::null()) } } 28
  29. Rustでコードを書く #[no_mangle] pub extern fn Init_wasabi() { unsafe { let

    rb_mod = class::rb_define_module(str_to_cstring("Wasabi").as_ptr()); class::rb_define_singleton_method(rb_mod, \ str_to_cstring("sum").as_ptr(), rb_sum as CallbackPtr, 2); class::rb_define_singleton_method(rb_mod, \ str_to_cstring("call_to_s").as_ptr(), rb_call_to_s as CallbackPtr, 1); } } 29
  30. ビルドする $ rake build checking for cargo... yes /Users/sinsoku/.cargo/bin/cargo rustc

    --release --lib -- -C \ link-args=-L/Users/sinsoku/.rbenv/versions/2.7.0-preview1/lib \ -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress Compiling libc v0.2.66 Compiling ruby-sys v0.3.0 (https://github.com/steveklabnik/ruby-sys#1b4c1412) Compiling lazy_static v0.2.11 Compiling ruru v0.9.3 (https://github.com/d-unseductable/ruru#587f1c40) Compiling wasabi v0.1.0 (/Users/sinsoku/.ghq/github.com/sinsoku/wasabi) Finished release [optimized] target(s) in 4.15s wasabi 0.1.0 built to pkg/wasabi-0.1.0.gem. 30
  31. RSpecで動作確認 RSpec.describe Wasabi do describe '.sum' do it "1 +

    2 = 3" do expect(Wasabi.sum(1, 2)).to eq 3 end end describe '.call_to_s' do context 'class with :to_s defined' do subject do klass = Class.new klass.define_method(:to_s) { 'foo' } Wasabi.call_to_s(klass.new) end it { is_expected.to eq 'foo' } end end end 31
  32. Rustで書いたコードが動いた 32

  33. Rustでgemを作るときの課題 • ! stableのgem/crateだと動かない • なぜかリリースされない • " 参考資料が少ない •

    C拡張に関するドキュメント • Ruby本体、ruru、ruby-sysのソースコード OSSはコードリーディングできるので便利 33
  34. Run a Rails app that uses Rust extensions on Docker

    • https://github.com/sinsoku/rusty_rails • https://github.com/sinsoku/wasabi 話す時間が残っていれば... 34