by Renee French. (http://reneefrench.blogspot.com/) The design is licensed under the Creative Commons 4.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher
gem • About go-gem-wrapper • Road to go-gem-wrapper • Use “ruby.h” in go building • Add --ext=go to bundle gem command • Pros/Cons of Go gem • Real use case 12 Agenda
work now (Go 1.24) ◦ a later mention 2. To make a gem in Go, you need to copy and paste a lot of patches. ◦ e.g. ▪ ext/gem_name/extconf.rb ▪ ext/gem_name/my_gem.go ▪ ext/gem_name/go.mod ▪ bindings ▪ etc… 16 Past issues
create example/lib/example.rb create example/lib/example/version.rb (snip) create example/ext/example/extconf.rb create example/ext/example/example.h create example/ext/example/example.c Gem 'gem_name' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html bundle gem <gem_name> –ext=c 21
a Go gem right after bundle gem • This is a temporary tool until bundle gem --ext=go is added to the bundler ◦ c.f. https://github.com/rubygems/rubygems/pull/8183 (a later mention) 22 Run “patch_for_go_gem.rb”
/path/to/example/ext/example/go.mod is created [INFO] /path/to/example/ext/example/example.c is updated [INFO] /path/to/example/ext/example/extconf.rb is updated [INFO] example/example.gemspec is updated Run “patch_for_go_gem.rb” 23
`go build` create_go_makefile("example/example", go_build_args: "-gcflags='all=-N -l'") create_go_makefile Add a task and hacks to build Go against the “Makefile” generated by create_makefile. (similar to create_rust_makeflile) 38
Type mismatch between outside and inside of module ◦ C pointer ◦ deprecated functions ◦ Removed functions ◦ Supports https://pkg.go.dev/ 3. Go with CRuby 42 Road to go-gem-wrapper
• However, cgo has characteristics that depend on Go's internal implementation and can be broken by Go version upgrades • c.f. “Why Go's old code almost never stops working“ ◦ https://zenn.dev/catatsuy/articles/fda1e42acad421 (ja) 49 Tips
• There are about 2,000+ C functions defined in ruby.h • About 1,100 functions can be included and actually used • At first (about 30 functions), I wrote them by hand, but I decided to generate them automatically from ruby.h because it's just too hard. 52 2. Generate Go’s bindings from “ruby.h”
are many #ifdef branches in “ruby.h” for each OS and build environment. • e.g. 57 Result https://github.com/ruby/ruby/blob/v3_4_2/internal/process.h#L14-L20 #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> /* for mode_t */ #endif #ifdef _WIN32 # include "ruby/win32.h" /* for mode_t */ #endif
we build by creating a file similar to “ruby_linux_amd64.go” or “ruby_darwin_arm64.go” (Build constraints) • But this increases the amount of code, and CI also needs to prepare all build constraints • I gave up on it because it would be too hard maintain ◦ https://github.com/ruby-go-gem/go-gem-wrapper/pull/117#issuecomm ent-2336583098 58 Result
• https://github.com/oxidize-rb/rb-sys ◦ Auto-generate Rust code from C API using bindgen ◦ https://rubygems.org/gems/rb_sys : Helpers for compiling Rust extensions for ruby • https://github.com/matsadler/magnus ◦ High level Ruby bindings for Rust ◦ depends on rb-sys 62 Architecture of Rust gem
go-gem-wrapper • go-gem-wrapper doesn’t include high layer API similar to magnus (maybe…) • I created go-gem-wrapper by myself 63 What is go-gem-wrapper
value in argument to ruby.RbNum2Long ./dummy.go:17:35: cannot use b (variable of type _Ctype_ulong) as ruby._Ctype_ulong value in argument to ruby.RbNum2Long ./dummy.go:21:39: cannot use sum (variable of type _Ctype_ulong) as ruby.Long value in argument to ruby.RbLong2NumInline make: *** [dummy.bundle] Error 1 Build error!!!!! 72
Go types. Because the translations are unexported, a Go package should not expose C types in its exported API: a C type used in one Go package is different from the same C type used in another. ref. https://github.com/golang/go/issues/13467 73 Answer. cgo specification
◦ char* (when RSTRING_PTR and RSTRING_END returns) 79 Q1. C type char* -> Go type ??? https://github.com/ruby/ruby/blob/v3_4_2/include/ruby/internal/core/rstring.h#L408-L416 /** * Queries the contents pointer of the string. * * @param[in] str String in question. * @return Pointer to its contents. * @pre `str` must be an instance of ::RString. */ static inline char * RSTRING_PTR(VALUE str)
in C // // Original definition is following // // VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv) func RbFuncallv(recv VALUE, mid ID, argc int, argv []VALUE) VALUE { return VALUE(C.rb_funcallv(C.VALUE(recv), C.ID(mid), C.int(argc), toCArray[VALUE, C.VALUE](argv))) } https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_4_generated.go#L3885-L3892
// RbCvarFind calls `rb_cvar_find` in C // // Original definition is following // // VALUE rb_cvar_find(VALUE klass, ID name, VALUE *front) func RbCvarFind(klass VALUE, name ID, front *VALUE) VALUE { var cFront C.VALUE ret := VALUE(C.rb_cvar_find(C.VALUE(klass), C.ID(name), &cFront)) *front = VALUE(cFront) return ret } https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_4_generated.go#L2278-L2288
calls `rb_define_variable` in C // // Original definition is following // // void rb_define_variable(const char *name, VALUE *var) func RbDefineVariable(name string, v *VALUE) { char, clean := string2Char(name) defer clean() C.rb_define_variable(char, (*C.VALUE)(v)) } https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_4_generated.go#L2688-L2698
bindings should not be generated 92 Issue 3. deprecated functions $ bundle exec rake build_all /Users/sue445/.rbenv/versions/3.4.1/include/ruby-3.4.0/ruby/internal/core/rdata.h:314 :1: note: 'rb_data_object_get_warning' has been explicitly marked deprecated here cgo-gcc-prolog:4427:11: warning: 'rb_data_object_wrap_warning' is deprecated: by TypedData [-Wdeprecated-declarations]
rb_data_object_make ◦ https://bugs.ruby-lang.org/issues/20265 ◦ https://bugs.ruby-lang.org/issues/18290 • Calling a non-existent C function from within Go code will result in an error at build time 95 Issue 4. Removed functions
3.3 bindings need to be able to use it as these are • It was necessary to use different Go bindings depending on the version of Ruby at the time of build 96 Issue 4. Removed functions
RIGHTS ARE RESERVED BY ROBOTS. // WARNING: This file has automatically been generated // Code generated by ruby_h_to_go. DO NOT EDIT. //go:build ruby_3_4 package ruby https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_4_generated.go#L1-L8 custom build tag
libraries and modules ◦ e.g. https://pkg.go.dev/github.com/ruby-go-gem/go-gem-wrapper ◦ Similar to https://docs.ruby-lang.org/ + https://rubydoc.info/ 101 Issue 5. Supports https://pkg.go.dev/
want 1,100 bindings not showing up on https://pkg.go.dev/ • I thought about building this myself and hosting this on GitHub Pages, but I thought this would be confusing to have Go module references published outside of https://pkg.go.dev/ 104 Why?
RubyHToGo.config.default_tag other_tags = RubyHToGo.config.available_tags - [RubyHToGo.config.default_tag] condition = other_tags.map { |tag| "!#{tag}" }.join(" && ") <<~GO // FIXME: https://pkg.go.dev/ doesn't support custom build tag. // Therefore, if no build tag is passed, treat it as the default tag //go:build #{ruby_build_tag} || (#{condition}) GO else <<~GO //go:build #{ruby_build_tag} GO end https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/_tools/ruby_h_to_go/lib/ruby_h_to_go/go_util.rb#L27-L45
RESERVED BY ROBOTS. // WARNING: This file has automatically been generated // Code generated by ruby_h_to_go. DO NOT EDIT. // FIXME: https://pkg.go.dev/ doesn't support custom build tag. // Therefore, if no build tag is passed, treat it as the default tag //go:build ruby_3_3 || !ruby_3_4 package ruby https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_3_generated.go
LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. // WARNING: This file has automatically been generated // Code generated by ruby_h_to_go. DO NOT EDIT. // FIXME: https://pkg.go.dev/ doesn't support custom build tag. // Therefore, if no build tag is passed, treat it as the default tag //go:build ruby_3_3 || (!ruby_3_4 && !ruby_3_5) package ruby https://github.com/ruby-go-gem/go-gem-wrapper/blob/v0.7.3/ruby/function_ruby_3_3_generated.go
Functions created in native extension (not just Go) should be tested from Ruby ◦ Because this is called from Ruby • Functions created in pure-Go should be tested from Go ◦ Because this is called from Go 120 Use case 1. go test
be able to execute • “ruby.h” is needed to execute go build in Go gem • Various environment variables are required to build with “ruby.h” 122 Problem: go test requires building with “ruby.h”
# Print build envs for `go build` rake go:build_tag # Print build tag rake go:fmt # Run go fmt rake go:test # Run go test rake go:testrace # Run go test -race rake go tasks 126
desc "Run golangci-lint" task :lint do go_task.within_target_dir do sh "which golangci-lint" do |ok, _| raise "golangci-lint isn't installed. See. https://golangci-lint.run/welcome/install/" unless ok end build_tag = GoGem::Util.ruby_minor_version_build_tag sh GoGem::RakeTask.build_env_vars, "golangci-lint run --build-tags #{build_tag} --modules-download-mode=readonly" end end end e.g. Use golangci-lint in Go gem 129 https://github.com/ruby-go-gem/go-gem-wrapper/tree/v0.7.3/_gem#example-add-additional-tasks
is not good at (and Go is very good at) • Go's features not found in other languages ◦ goroutine: lightweight thread ▪ https://go.dev/tour/concurrency/1 ◦ channel: Messaging between multiple goroutine ▪ https://go.dev/tour/concurrency/2 137 Pros 2
and multi process are not eligible ◦ Because the results depend on the number of CPU cores on the machine being benchmarked ◦ This is not fair to compare these to goroutine 138 Ractor vs Fiber vs goroutine
◦ CRuby provides both variable length argument and non-variable length argument versions of its functions. ◦ If we only have functions with variable-length arguments, it is a bit of a problem ▪ e.g. rb_raise 143 Cons 2