一年間で作ってきたリポジトリをさかのぼる

はじめに

この記事は、俺今年頑張ったAdvent Calendar 2019 の13日目の記事です。

 

タイトル通り、一年間で作ってきたリポジトリを振り返っていこうと思います。なお、GitHubで公開しているもののみに限定しています。

 

リポジトリ

今年一年で作ったリポジトリの数は、ちょうど200個!(2019年12月13日現在)意外と作ってるなぁという印象。

 

中にはサンプルとして組んでいるものなども結構あるので、意外とあれこれやってきたんだなあと実感。

 

言語別だと一番作っているのはRubyで、次点でPHPC++という感じですね。RubyC++が多いのは個人的な趣味で良く書くことが多いからだと思うけど、PHPが意外と多かったのには驚いた。

今年は、仕事でPHPを書くことが多かったのでそのせいかな……?

 

個人的な抱負としては来年もC++Rubyをガンガン書いていきたい。で、時点でRustとかGoをやっていきたい。まあ、その余裕があるかはわかんないけどね。

 

特に面白そうなもの 

AutoNSFWChecker

github.com

MastodonのLTLを監視して、卑猥な画像などを自動判定してくれるBotですね。Google Cloud Vision APIを使っているので比較的シンプルに実装しています。

ただ、LTLが活発だと結構コストがかかるのでこれをそのまま導入するのはお勧めできない感じ

RubyGoogle Cloud Vision を使ってみた方はこれを参考にしてみるといいかも

 

MastodonDestroyButton

github.com

ネタで作ったMastodonサーバーの爆破ボタン。AmazonDashボタンを使って、押された際に自分のMastodonサーバーをスナック感覚で爆破できる代物です。とはいえAmazonDashボタン自体が販売中止だそうなので、今後使う場面はなさそう

 

ARcpp

github.com

C++/Vue.jsで作ったAR読込のサンプル。結構シンプルに作ってあるのでAR関係のサンプルを作るときにたまに見返してたりする

 

MyFirstRails

github.com

Railsチュートリアル集として作ったリポジトリ。色々と作っているのでたまに自分用のリファレンスみたく使ったり。最近メンテナンスできていないので、今年の年越しはメンテしたい。

 

face_under_construction

github.com

顔面工事というハッシュタグをブーストするBotですね。実装にあたってAWS Lambdaとか使ったりしていい勉強になりました。あと今年の分散SNS萬本やRubyistMagazineでも作った際のあれこれを寄稿させてもらったりといいきっかけになったやつですね。

 

arukas

github.com

ArukasというDockerイメージを元に気軽にデプロイできるサービスをRubyで実行できるようにしたものですね。とはいえ、Arukas自体が来年一月にサービスが終了するのでもう使うことはなさそう……。

 

conpass-shimane-line

github.com

Connpassというイベント管理サービスのAPIを使って、LINEに直近のイベントをシェアしてくれるBotですね。実装はAWS lambdaを使っているので先ほどの顔面工事Botと同じ感じですね。意外と重宝しているので今後もメンテナンスしていきそう。

 

sol

github.com

C++/Svelte/Stimulusを使って自作したWebフレームワーク。Railsライクなコマンドでアプリのひな型とか作れるので重宝してる。ちょっとしたWebアプリケーションとか作って納品しようかなってときには楽で助かってる。

 

bootstrap_plugin_card_slide

github.com

WordPressプラグインをスライド風に表示して紹介できるプラグイン。実装が結構重いので今後修正していきたい。

 

tatara

github.com

Ruby拡張機能として型を使えるようにしてみたもの。おそらく今年一番コミットしたりしてたリポジトリ。Riceを使ってたんだけど、最近C++のみの実装に移行した。tataraは今後もじっくりと開発を続けていきたいところ。

 

おわりに

意外とアレコレ作ってきたなぁという感想。あとジャンルが結構違ったりするので雑食だなぁとも思った。

とりあえず、来年も頑張るかな?

C++で作るRuby拡張

はじめに

この記事は「Ruby Advent Calendar 2019」の8日目の記事です。

C++でのRuby拡張実装について、つらつらと書いている記事になります。

内容としてはTataraというRubyで型を使えるライブラリを作ってみたで紹介した自作Ruby拡張を作るにあたって得たC++でのRuby拡張実装知見の記事になります。

Ruby拡張って?

皆さんが普段使っているRuby(ここではCRubyのことです)はCによって実装されています。ですので、Cを使ってRuby拡張機能を作成することもできます。

つまり、Cで既に作成されているライブラリなどをRuby拡張として作成することができるというメリットがあります。CでRuby拡張を実装した場合、Rubyで実装するよりも高速に処理できるケースもあるようです。

実際にCで拡張機能が実装されているgemとしてはsqlite3mysql2などがあります。

またRustやC++Ruby拡張機能を作成するケースもあります。

例えば最近面白いなぁと思ったのはRustでのRuby拡張を実装できるHelixですね。HelixRustを使うことでCやC++よりも安全にRuby拡張を書くことができます。

また実装コード自体もかなり読みやすく以下のようなコードでクラスとメソッドを実装できます(※ HelixのREADMEより引用)。

ruby! {
    class Console {
        def log(string: String) {
            println!("LOG: {}", string);
        }
    }
}

ただHelix公式のチュートリアルではRails向けに拡張機能を実装する内容になっています。そのためRuby向けの拡張を作成する際のドキュメントがあまりなく、少し辛いところがあります。

実際にHelixでRuby拡張を作成しているものとしては以下の記事などがあります。

ref: Rustでgemを書く際のハマりどころ in 2017

ref: Writing Ruby gems with Rust and Helix

またC++ではRiceExt++などのRuby拡張を実装できるライブラリも存在しています。

RubyKaigi 2017ではImprove extension API: C++ as better language for extensionにてC++でのRuby拡張実装について紹介されています。

興味のある方はそちらも確認してみると良いでしょう。

今回はC++でのRuby拡張の実装方法について解説します。具体的にはRiceExt++C++のみでの実装方法などを解説していきます。

つくるもの

今回は、Helloというクラスを作成し、Hello Ruby Extension!と画面に表示するsayというメソッドを実装します。

具体的には以下のようなコードが実行できるRuby拡張を実装していきます。

require 'hello'

Hello.new.say
# => "Hello Ruby Extension!"

今回はRiceExt++C++でそれぞれ実装していきます。

今回の記事作成にあたって各ライブラリでの実装サンプルをGitHubに上げておきました。興味のある方はこちらも見ると良いかも。

S-H-GAMELINKS/RubyAdventCalendarExtensionSample

実装

Riceでの実装

Riceとは?

Riceとは、C++を使ってRuby拡張を簡単に作成できるライブラリになります。

RiceはgemとしてRubyGemsからインストールすることができます。

gem install rice

これでRiceが使えるようになります!

ちなみに、実際にRiceを使ったサンプルコードは以下のようになります。

#include <iostream>
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Hello {
    public:
        Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

extern "C" {
    void Init_hello() {
        Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
            .define_constructor(Constructor<Hello>())
            .define_method("say", &Hello::say);
    }
}

このようにRiceを使う場合、非常に簡単にRuby拡張を作ることができます。

またC++のテンプレートなどを使って以下のようなコードを書くこともできます。

template <class T>
class CppArray {
    public:
        CppArray<T>() {};
};

Data_Type<CppArray<int>> rb_cIntArray = define_class<CppArray<int>>("IntArray")
    .define_constructor(Constructor<CppArray<int>>());

Riceを使うメリットととしては、非常に簡単にC++でのRuby拡張を作ることができる点ですね。C++のライブラリなどをRubyで使えるようにするラッパーなどは、Riceを使って実装するといいかもしれません。

デメリットとしては、日本語のドキュメントもあまりないことと、開発自体があまり活発でない印象があることですね。

日本語で書かれた記事はあまり(Rice以外での実装とかはあったりする)なく、IBMRice を使用して Ruby の拡張機能を C++ で作成するが日本語で唯一詳しく書かれたRiceのチュートリアルになりそうです、

英語が読める方であれば、こちらのドキュメントを読み解けばよいかと思います。

GitHubリポジトリでのコミットログなどを見るた印象ではあまり開発が活発な印象はないです。最近、いくつかPull Requestが取り込まれてはいるようですが……。 そのため、Rice側の開発が打ち切られると辛いことになりそうな気配がありますね……。

とはいえ、大きな変更が入る可能性は少ないのでとりあえずC++でのRuby拡張を作る分には良いライブラリだと思います。

実装

それでは、Riceを使ってRuby拡張を実装してみましょう。

なにはともあれ、Riceをインストールしましょう。

gem install rice

インストールが無事終了した後は、extconf.rbというファイルを作成します。これはC++のコードをビルドするMakefileを自動生成するためのファイルになります。CでRuby拡張を作る場合も同様にextconf.rbを作成します。

require 'mkmf-rice'

create_makefile('hello')

mkmf-riceはRiceを使ってかかれたC++のソースをもとにMakefileを作成するためのライブラリになります。ちなみに、Cで拡張機能を実装する場合はmkmfというライブラリを読み込んでMakefileを自動生成していますね。

またcreate_makefileに渡している文字列がビルドされた拡張ライブラリの名前になります。

次に、hello.cppextconf.rbと同じ階層に作成します。

#include <iostream>
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Hello {
    public:
        Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

extern "C" {
    void Init_hello() {
        Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
            .define_constructor(Constructor<Hello>())
            .define_method("say", &Hello::say);
    }
}

軽くコードの解説をすると、以下の二行でRiceのヘッダーを読み込んでいます。

#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

RiceではData_Typeを使い、既存のクラスをもとにRuby向けにコンバートしています。

Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")

上記のコードではC++で定義したHelloクラスをRubyで呼び出すHelloというクラスに変換しています。

.define_constructor(Constructor<Hello>())

.define_constructor(Constructor<Hello>())ではC++で定義したHelloクラスのコンストラクタ(Rubyでいうところのinitializeのようなもの)を使って、RubyHelloクラスのインスタンスを作成できるようにしています。 つまり、Rubyのinitializeを実装しています。

最後に.define_method("say", &Hello::say);sayというメソッドをHelloクラスに追加しています。

.define_method("say", &Hello::say);

これでC++側での実装は完了です。

次に、extconf.rbを実行してMakefileを生成します。

ruby extconf.rb
# => Makefileを自動生成

あとは、makeコマンドでビルドすればhello.ohello.soが生成されていると思います。

make
# => hello.o と hello.so が生成される

最後に、作成したRuby拡張を実際に動かしてみましょう。hello.rbを以下のように作成して実行してみましょう。

require './hello.so'

Hello.new.say
ruby hello.rb
# => Hello Ruby Extension!

Hello Ruby Extension!と表示されていればOKです!

Ext++での実装

Ext++とは?

Ext++はRice同様にC++を使って、Ruby拡張を作成できるライブラリです。

Ext++もRubyGemsで配布されているのでgemとしてインストールできます。

gem install extpp

Ext++での実装は以下のようになります。

#include <iostream>
#include <ruby.hpp>

RB_BEGIN_DECLS

void Init_hello() {
    rb::Class klass("Hello");

    klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) {
        return Qnil;
    });

    klass.define_method("say", [](VALUE rb_self) {
        std::cout << "Hello Ruby Extension!" << std::endl;
        return Qnil;
    });
}

RB_END_DECLS

Ext++ではC++ラムダ式を引数に渡して実装することができる点が特徴的です。そのためラムダ式をうまく使うことでRubyのメソッドとC++の実装を一度に書くことができます。

また、Ext++ではruby.hppをインクルードするだけで良いところも便利です。Riceの場合、必要なヘッダーを個別に読み込まなければならず

Riceではラムダ式を使ってメソッドの定義などはできないため、ラムダ式でメソッドを定義したい人はExt++を使うと良いかもしれません

Ext++を使うメリットとしては、実装が一か所で済む点かなと思います。また開発者が日本の方(というか @kou さん)ですので開発者本人にあって話が聴けるという点もメリットかもしれません。

デメリットとしては、サンプルのコードが一つしかなく、人によってはどのように実装を進めていけばいいのかが分かりにくい時がある点でしょうか?その点に関しては今後Pull Requestなどでサンプルコードを投げれたらと思っていますね。 また開発バージョンであり、今後のバージョンアップでは大きな変更も入る可能性もありそうです。

しかしながら、開発者本人に直接話を聞くことができそう(日本人からすると)なので採用するメリットはかなり大きいと思います。

またRiceと違い、Rubyの実装自体に近い実装コードを書くのでCRubyの実装を学んでみたいという人にもオススメかもしれませんね。

実装

それでは、Ext++を使ってRuby拡張を実装していきましょう。

まずはExt++をインストールします。

gem install extpp

インストール完了後、Riceでの実装の時と同じようにextconf.rbを作成します。

require 'extpp'

create_makefile('hello')

Riceの時とおおよそ同じコードですね。違う点としてはmkmf-riceではなく、extppを読み込んでいます。

次に、hello.cppextconf.rbと同じ階層に作成します。

#include <iostream>
#include <ruby.hpp>

RB_BEGIN_DECLS

void Init_hello() {
    rb::Class klass("Hello");

    klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) {
        return Qnil;
    });

    klass.define_method("say", [](VALUE rb_self) {
        std::cout << "Hello Ruby Extension!" << std::endl;
        return Qnil;
    });
}

RB_END_DECLS

Ext++ではrb::Classで新しいクラスを作成します。また、作成したklassdefine_methodを使うことで必要なメソッドを新しく定義しています。

QnilRubyでのnilを返しています。CRubyのメソッドなどでnilが返ってきているメソッドでは、子のようにreturn Qnil;と書かれています。興味のある方はRuby Hack Challenge Holidayに参加したり、GitHubruby/rubyのコードを読んでみると良いかもしれません。

あとは。extconf.rbを実行し、Makefileを生成します。

ruby extconf.rb
# => Makefileが生成される

その後、makeで作成したRuby拡張をビルドします。

make
# => hello.o と hello.soが生成される

最後にhello.rbを以下のように作成し、実行してみましょう。

require './hello.so'

Hello.new.say
ruby hello.rb
# => Hello Ruby Extension!

Hello Ruby Extension!と表示されていればOKです!

C++での実装

実装

最後にC++でのみで作成するRuby拡張について紹介します。

まずはextconf.rbを作成します。

require "mkmf"

create_makefile("hello")

mkmfはCRubyに添付されているRuby拡張のためのMakefile作成ライブラリですね。

次に、hello.cppを以下のように作成します。

#include <ruby.h>
#include <iostream>

class Hello {
    public:
        Hello() {};
        ~Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

static Hello* get_hello(VALUE self) {
    Hello *ptr;
    Data_Get_Struct(self, Hello, ptr);
    return ptr;
}

static void wrap_hello_free(Hello *ptr) {
    ptr->~Hello();
    ruby_xfree(ptr);
}

static VALUE wrap_hello_alloc(VALUE klass) {
    void *ptr = ruby_xmalloc(sizeof(Hello));
    ptr = std::move(new(Hello));
    return Data_Wrap_Struct(klass, NULL, wrap_hello_free, ptr);
}

static VALUE wrap_hello_init(VALUE self) {
    return Qnil;
}

static VALUE wrap_hello_say(VALUE self) {
    get_hello(self)->say();
    return Qnil;
}

extern "C" {
    void Init_hello() {
        VALUE rb_cHello = rb_define_class("Hello", rb_cObject);

        rb_define_alloc_func(rb_cHello, wrap_hello_alloc);
        rb_define_private_method(rb_cHello, "initialize", RUBY_METHOD_FUNC(wrap_hello_init), 0);
        rb_define_method(rb_cHello, "say", RUBY_METHOD_FUNC(wrap_hello_say), 0);
    }
}

ポイントとしては#include <ruby.h>Ruby拡張の実装で使用するマクロや関数などを呼び出している点ですね。これがないとRuby拡張を実装することができません。

またget_hello関数はRubyインスタンスを引数に受け取って、C++インスタンスのポインタを返しています。この関数を使うことでC++のクラスのメソッドをラップ関数から呼び出して使うことができるようになります。

wrap_hello_free関数はRubyGCが呼び出された際にメモリから解放する際の処理がかかれた関数になります。

wrap_hello_allocインスタンスを作成する際のアロケータになります。wrap_hello_initRubyでのinitializeになりますね。

あとは、extconf.rbを実行し、makeを実行してビルドしてみましょう

ruby extconf.rb
# => Makfileが生成される

make
# => hello.o と hello.so が生成される

最後に、hello.rbを以下のように作成して実行しましょう。

require './hello.so'

Hello.new.say
ruby hello.rb
# => Hello Ruby Extension!

Hello Ruby Extension!と表示されていればOKです!

おわりに

C++でのRuby拡張についてRice、Ext++、C++それぞれでのでの実装を紹介しました。意外と簡単そうと思っていただければ幸いです。

あと今回の記事ではC++をベースに紹介しましたが、もちろんCでの実装を行う方法もあります。むしろ、そちらのほうが参考になる記事が多いので、Ruby拡張を作る際にはCで作ると良いかもしれません。

あと、この記事でRubyの実装に興味を持たれた方はRuby Hack Challenge Holidayなどに参加してみると良いかもしれません。

意外と簡単にC++でもRuby拡張機能を作ることができるので、今後もC++の良さげなライブラリなどをRuby向けに実装していきたいと思います。

参考記事

ref: Rice ref: Ext++ ref: Improve extension API: C++ as better language for extension ref: Rice を使用して Ruby の拡張機能を C++ で作成する ref: Rice - Ruby Interface for C++ Extensions ref: ko1/rubyhackchallenge ref: ruby/ruby ref: C++言語で簡単なRuby拡張ライブラリを書いてみた ref: Rubyの拡張ライブラリの作り方 ref: Rubyソースコード完全解説 ref: TataraというRubyで型を使えるライブラリを作ってみた

Hanami向けにBlumaのラッパーgemを作った

HanamiというRubyのWebフレームワーク向けにBlumaってCSSライブラリのラッパーgemを作ってみた

 

github.com

 

 

使い方は簡単で、Gemfileにhenami-blumaを追加するだけ。

 

あとは、<%= stylesheet 'bluma' %>のように使いたいテンプレートで呼び出すだけ

 

実装にあたっては以下のラッパーgemを参考にさせて頂きました。

 

github.com

 

TataraというRubyで型を使えるライブラリを作ってみた

はじめに

この記事は最近僕が作っているRubyで型を使えるライブラリの紹介記事です。

対象読者としては、Rubyで簡単に型のようなものを使ってみたい人、またはC++でのRuby拡張を作ってみたい人を想定しています。

Tataraの紹介をしつつ、簡単なC++でのRuby拡張実装についても触れていきます。

作ったもの

TataraというRubyで簡単な型を使えるようにするRubyExtensionを作りました。RubyGemsにもリリースしており、v0.2.0が現在リリースされている最新バージョンになります。

v0.2.0まではRiceというRubyExtensionを簡単にC++で使用していました。しかし、GCへの管理追加が少しややこしかったり、RiceでC++のコードをRuby向けにラップしているため処理が遅くなったりするという問題がありました。そこで次期リリースではC++のみでの実装に書き直し、GCへの管理追加や処理速度向上を図っています。

ちなみに、次にリリースするv0.3.0は11月中にリリースする予定で実装を進めています。

サンプルコード

例えば、Tataraを使うことで以下のようなコードを実行できます。

require 'tatara'

@int_container = Tatara::IntVector.new

(1..10).each{|i| @int_container << i}
# Insert 1..10 to @int_container

@int_container.map{|i|
    puts i
    # 1..10 value is shown
}

@int_containerは整数値のみが保存できるVectorになります。浮動小数点数値なども代入できますが、整数値に暗黙的に変換されます。

@int_container = Tatara::IntVector.new
@int_container << 4.2
# => Push 4

ちなみに、文字列を保存しようとするとエラーになります。

@int_container = Tatara::IntVector.new
@int_container << "42"
# => Error!

このようにTataraでは簡単な型が使えるようにしてあります。

作ろうと思ったきっかけ

Ruby自体は非常に書きやすく、楽しくコーディングができる素晴らしい言語です。

ですが、元々静的型付け言語(C/C++など)を触ることが多かったため「Rubyにも型のようなものが欲しいなぁ……」と思うこともありました。

そんな折、RiceextppなどでRubyExtensionを作れそうだとわかり、「面白そうなので作ってみよう !」となり作ってみました。

やったこと

開発初期のころはRiceでのRubyExtension作成についてドキュメントや記事などを漁りに漁っていました。で、Riceで実装できそうなことがわかったので少しづつ実装を進めていった感じですね。

最初はIntegerやFloatなどを実装し、その後VectorやMapなどを実装していきました。

v0.1.0をリリースした辺りでGCへの管理追加などがややこしいことが判明し、Riceを外す方法を模索し始めました。extppなどでのRubyExtension実装ついても調べたりしました。

結果として、Ruby自体の実装コードなどを読みつつ、以下の記事を参考にRiceを外していくことにしました。そのおかげでRubyの実装なども読むことができたので良い経験が積めたと思います。

Riceでの実装

Riceでは以下のようにC++で作成したクラスをラップしてRuby向けに使用できるようにできます。

#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

extern "C" {
    void Init_tatara() {
        Module rb_mTatara = define_module("Tatara");

        Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")
            .define_constructor(Constructor<Integer>())
            .define_method("value", &Integer::return_value)
            .define_method("value=", &Integer::assignment);
    }
}

RiceではC++で定義したクラスをData_TypeRubyのクラスへとラップしてくれます。またモジュール名などはModule rb_mTatara = define_module("Tatara");のように作成することができます。

Module rb_mTatara = define_module("Tatara");

Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")

上記のコードではTataraモジュール内にIntegerクラスを作成しています。

.define_constructor(Constructor<Integer>())

またこのコードではC++Integerクラスのコンストラクタをnewとして利用しています。

あとは、以下のように必要なメソッドをdefine_methodでクラスに追加しています。

.define_method("value", &Integer::return_value)
.define_method("value=", &Integer::assignment);

define_methodはメソッド名と実行するメソッドを引数として渡すことで、C++側で定義したメソッドをRuby側で使用することができます。

これにより、以下のようなRubyのコードが実行できます。

@integer = Tatara::Integer.new
@integer.value = 42
puts @integer.value
# => 42

このようにRiceを使うことでC++で定義したクラスをRubyで使用することができます。

ちなみに、テンプレートなどを使う場合は以下のように型を指定する必要があります。

Data_Type<CppArray<int>> rb_cIntArray = define_class_under<CppArray<int>>(rb_mTatara, "IntArray")
    .define_constructor(Constructor<CppArray<int>>());

C++への実装書き直し

先ほどRiceで書いていたコードは以下のように書き直すことができます。

#include <ruby.h>

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}


static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

extern "C" {
    void Init_tatara() {
        VALUE mTatara = rb_define_module("Tatara");

        VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

        rb_define_alloc_func(rb_cInteger, wrap_int_alloc);
        rb_define_private_method(rb_cInteger, "initialize", RUBY_METHOD_FUNC(wrap_int_init), 0);
        rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
        rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);
    }
}

C++のクラスをそのままRuby側で呼び出すことはできないので、以下のような関数を作成し、Ruby側から呼び出せるようにしています。

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

モジュールはrb_define_moduleで作成でき、以下のようにTataraというモジュールを作成しています。

VALUE mTatara = rb_define_module("Tatara");

また、以下のコードではIntegerクラスをTataraモジュール内に作成し、その基底クラスにObjectクラスを使用しています。

VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

Rubyソースコード内ではVALUE型を使うことでインスタンスの値などを取得することができています。

getIntegrではインスタンス自身からC++のクラスのポインタを取得し、そのポインタを使ってクラスで定義されているメソッドを実行しています。 例えば以下のようにreturn_valueメソッドを呼び出すことができます。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

またwrap_int_freeではRubyGCでメモリが解放される際の処理を実装しています。

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

wrap_int_allocRubyのコードで新しくnewインスタンスを作成した際のアロケーションを実装しています。

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

rb_define_alloc_funcアロケーション時に実行する関数を指定できます。

rb_define_alloc_func(rb_cInteger, wrap_int_alloc);

wrap_int_initinitializeを実装しています。

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

最後に、以下のようにC++で定義されたクラスのメソッドを実行するラップ関数を作成します。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

INT2NUM()NUM2INT()はCとRubyとの間でデータの変換を行う処理になります。この処理を間に挟むことでRuby内の変数の値などをC++側に渡すことができます。

あとは、作成したラップ関数を以下のようにメソッドとして追加します。

rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);

rb_define_methodは第一引数にメソッドを追加するクラスのVALUEを渡します。第二引数にはメソッド名、第三引数には実行する関数を渡します。
RUBY_METHOD_FUNCでは渡している関数ポインタをRubyのメソッドとしてキャストしています。
第四引数は、そのメソッドが受け取る引数の数になります。0の場合は引数はなく、1などの場合はその数だけ引数を受け取ります。なお、-1の場合は可変長引数として受け取っているようです(RubyのArrayクラスの実装などで見る限り)。

このようにしてC++Rubyのクラスを作成することができます!

おわりに

今回作成したTataraの紹介をしつつ、C++でのRuby拡張の実装などについても触れてみました。普段使っているRubyの内部コードについても少し触れましたので、興味が湧いた方もおられるかもしれません。

ここから宣伝。

なお、東京近隣の方であればRuby Hacking Challenge HolidayというRubyの実装などをRubyコミッターから直々に聴けるというイベントがあります!
ちなみに、次回は「Ruby 2.7新機能紹介」や「Rails Girls Tokyo, More! 参加者の皆様と合同LT大会」を行うようです。

Ruby Hack Challenge Holiday #9 Ruby 2.7 + 年末LT大会

この記事を読んでRubyの内部に興味を持った方は是非参加してみるといいでしょう。

ちなみに、島根県浜田市ではRuby Hacking Challenge in Hamada.rbをHamada.rb内で開催しています。
僕のほうで内部のコードなど解説を行いますので、興味のある方は是非ご参加ください(なお、わかる範囲になりそうですが……)

Ruby Hacking Challenge in Hamada.rb

宣伝終わり

今後もゆっくりとTataraの実装を行いつつ、Ruby自体の実装への理解を深めていきたいと思います。

参考資料

Rubyソースコード完全解説

Rubyの拡張ライブラリの作り方

ko1/rubyhackchallenge

ruby/ruby

Mastodon v3.0.0rc1にアップデートした際のアレコレ

はじめに

Mastodon v3.0.0rc1がBump up されたので早速追従した際の出くわしたエラーの話

 

対象読者としては、さくらのスタートアップスクリプトMastodon鯖を構築した人らになるかな?

 

出くわしたもの

Mastodon v3.0.0rc1がBump upされたとのことでいつものようにmaster追従しようとしたところ

 

Error: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found

 

マイグレーション実行時に上記のエラーに出くわした。

 

調べてみるとMastodonで使用しているcharlock_holmesのバージョンが上がったっぽく、その影響で使用しているGCCのバージョンが変わったことが原因の模様

 

github.com

 

以前もcld3関連で同様のエラーに遭遇しており、「どうしたもんかなぁ……」と考えていた

 

そんな中、以下の記事を見つけ、最新のGCCをインストールすることでget kotonakiとなった

 

qiita.com

 

なので、もし同様のエラーで苦しんでいる鯖缶がいたら上記の記事を参考にGCCをインストールするとよいかもしれない

 

でも、以前のcld3の時のエラーも同様の解決方法したんだけどうまくいかなかったんだよね……。なんか手順間違えてたのやら……

C++のWebフレームワークっぽいものを作ってみた

作ったもの

github.com

 

実際のデモ

gamelinks007.net

 

やったこと

CLI11というコマンド入力をよしなにしてくれるライブラリを使って、sol new などの必要ようなコマンドを作成

https://github.com/CLIUtils/CLI11

 

入力されたコマンドをパースさせて、作成したテンプレートをC++17で追加されたfilesystem使ってコピーできるようにした

 

sol server や sol buildなどの開発用のコマンドなども追加したね

 

並行して、最低限度動くサンプルをcpp-httplib/Stimulus/Svelteで作成したり

 

今後

ORマッパーとかないのでよさげなライブラリを探しつつ、実装していこうかなぁって感じ

 

 

 

Creatodon2周年に際して思ったことアレコレ

はじめに

この記事は、Mastodon Golden Week Calendar の7日目の記事です

 

これまでとこれからの記事は下記URLより読むことができます

 

https://gw-advent.9wick.com/calendars/16

 

ちなみに、去年書いた記事はこちら

 

gamelinks007.hatenablog.com

 

今日書くこと

 

個人的にこの二年間で思ったことを徒然と書くだけです

 

Creatodonの2周年

 

えー、なんやかんやでCreatodonも無事2周年を迎えることができましたー

 

これも日頃の鯖缶の皆さんの知見であったり、ユーザーの皆様のおかげだと思います

 

この場を借りて感謝申し上げます

 

で、思ったこととか適当に

最近思ったこと

この二年でMastodonをめぐる環境ってかなり変わってきたよなーとか思いますねー

 

二コフレが鯖を閉鎖したり、JP鯖の管理者がぬるかるさんからきぼうソフトに移行したりと

 

でも、一ユーザーからしたらそんなことは大きな影響ではないんだよね

実際、ユーザー視点から言えば、他なサーバが止まったからといって何かがあるわけでもないしね(まあ、そのサーバ使ってるユーザーからすれば大問題だろうけども

 

ユーザーからすればサービスが安定して運営されてて、楽しければすべてOKなんだよね

 

だから、鯖缶がアレコレMastodonやFediverse全体のこととか話してても大して興味はないんじゃないかな?

 

というよりも、むしろ聞きたくないんじゃないかなぁと

テーマインスタンスとかだと、そのテーマについて話しがしたいのであって、Fediverseの話がしたいわけでもないしねー

 

まあ、そう言いつつ僕もアレコレ話をしているので矛盾してる感はあるんだけれどもねー

 

Creatodonの運営のアレコレ

他なテーマインスタンスとかどうされてるのか気になるところですが、Creatodonは基本的に放置してます

 

まあ、自主性を重んじるというか、単に管理するのが面倒くさいというか……

 

一番は面倒くささと「管理されるの嫌だよなぁ……」という個人的な思いでしょうかね

 

こう話さねばならないとか七面倒だし、別に自由に呟けばいいんじゃないんですかね?とかは思う

なので、特にジャンルの規定とかはしないし、好きなように使っていただければとは思う

 

とはいえ、サーバ運営に影響する内容とかだと処置しなきゃいけないし

 

その辺の尺度は、まあユーザーさんの自主性に任せてます

 

後、別にCreatodon以外にもサーバはあるので合わないと感じたら別なサーバに引っ越ししてもいいと思うんですよねー

 

「最近、あんまり浮上してないなー」とかを気にしないでもらいたいですし、合わないと感じてもTwitter見たく他に行き場所がないとかもないわけですし

 

好きなところに行ける自由さはFediverseにあるので、その自由を行使すればいいんじゃないかな

 

Fediverse全体に関しての雑感

個人的なところ、Fediverse全体は極端な人から柔軟な人まで幅広くいるカオスだと思ってる

 

で、「お一人鯖最高」の民もいれば、「LTL最高」の民もいる

その雑多なカオスな感じが好みではあるので、その状態を維持できれば面白そうだよなーとは思う

 

とはいえ、鯖缶の視点から行くと問題行為を働いたユーザーの話とかを呟いちゃったりするので完全なカオスとは言い難くなるかもだけれどね

 

あと時たまローカルルール的に「ブロックは負け」とかいう話も出ているけども別にいいんでない?

合わない人と何を話しても合うことはない時ってあるし、相手のためにも先にブロックしとけばいいんでない?

 

 

その辺も自由でいいんじゃないかと思ってる

 

今のFediverseは自由をうたってはいるけれど、一方ではローカルルール的なものを強いていたりとある種のユートピアさとディストピアさを重ねもった世界観かなぁと思う

 

ブレードランナーとかの感じに近いものを感じるので、個人的にはそういう感じも好きだけどねー

 

その他

今後、MastodonなどのAPなSNSが流行することがあるかはわからないけど、あったとしてもそれは大したことではないと思う

 

ユーザーからすれば今までのTwitter見たくあれこれ知り合いと話しやすくなるだけで、管理人からすればユーザー数が増えるだけ

 

それに今はPleromaやMisskeyなどMastodon以外にもAPでつながることのできるSNSも存在している

 

だから、Mastodonだけにユーザーが一極集中するわけでもないし、あんまし気にしなくても良いんじゃないかなと思ってる

 

まあ、それは少人数のMastodonサーバだからの話で、大所帯のところとかは大変だろうけどねー

 

あと、Twitterがつぶれるとかなっても新しい他なSNSに移行するだろうしねー

 

おわりに

まあ、何となくな感じでアレコレ書いて支離滅裂な感じもありますが、思ったことは大体書いたのでこれでいいかな

 

まあ、今後もゆるく気張らずにCreatodonの運営をやっていこうと思います

 

まあ、ハーモニーのトゥアレグ族のセリフにあるように「ほどほど」な感じでやっていこうと思いますー