mruby/c の仕様みたいなもの

[しまねソフト研究開発センター](https://www.s-itoc.jp)（略称 ITOC）にいます、東です。

この記事は、[mrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2024](https://qiita.com/advent-calendar/2024/mruby-family) の11日目に投稿しています。
今回は、今日(2024/12/11)現在の mruby/c の仕様みたいなもの、結果的に仕様になったもの、できること、できないこと、等をだらだらと書いていこうと思います。なお、執筆にあたり、[パーフェクトRuby 改訂2版](https://gihyo.jp/book/2017/978-4-7741-8977-2) の章立てを、大いに参考にさせてもらっています。


# 設計思想

* 実行時のメモリ(RAM)使用量を十分小さく保つ
* 機能的にメモリを多く使用しないと実現できない機能は、使用頻度などを総合的に考えて、実装しないかまたは代替手段を実装する
* 実行速度の考慮はするが、プライオリティーは メモリ使用量 > 実行速度 とする
* VMのみの実装とし、前段（コンパイラ）は mruby と共用する
* 複数のrubyプログラムを、同時実行することができる
* 実装を可能な限り簡潔に保つ

mruby/c の実行環境として、ワンチップマイコンを想定しています。そのため、32〜64KBのRAM容量で、ある程度実用的なプログラムを動作させることが必須と考えています。
また、九州工業大学との共同研究でもあり、教育用途として実装を可能な限り簡潔に保ちたいと思っています。（が、C言語の特性上そうも言ってられない箇所が随所に・・・）

では、各機能ごとに可否を書いていきます。


# class

v1.1から、クラス定義をサポートしています。

## インスタンス変数

v1.2から、インスタンス変数をサポートしています。

## クラス変数

サポートしていません。

```
class Class1
  @@class_var	# not supported.
end
```

## クラスメソッド、インスタンスメソッド
クラスメソッドと、インスタンスメソッドの区別はありません。ただし、self はちゃんと違うので、それを使えば区別可能です。
```
class Class1
  def method1
    if self == Class1
      puts "Class method"
    else
      puts "Instance method"
    end
  end
end

Class1.method1        # => "Class method"
Class1.new.method1    # => "Instance method"
```

この仕様が正しいかは、議論の余地があります。つまり将来変更の可能性があります。
しかし、クラスメソッドとインスタンスメソッドで同じ名前のメソッドを定義することは、利用者にとってほとんどの場合混乱の元になると考えられ、そういったこと自体をすべきでは無いとも言えます。

## 継承

ユーザー定義クラスの継承を、サポートしています。
```
class Class1
end

class SubClass1 < Class1
end
```

組み込みクラスの継承はできません。
```
class MyArray < Array   # Not supported
end
```
注：組み込みクラスへのメソッドの追加はできます
```
class Array
  def my_method
  end
end
```

## クラスのネスト

v3.3以降、問題なく動作します。

v3.2までは、2レベルまでサポートしており 3レベル以上のネストは、実行できるが予期せぬ挙動を示すかもしれません。

```
class Cls1	# OK
  class Cls2	# OK
    class Cls3	# not supported
    end
  end
end
```


# module

現リリースバージョンではサポートしていません。
master ブランチへは module サポートコードがマージされましたので、基本的な動作は可能です。

## include

サポートしています。

## 名前空間としての利用

サポートしています。
```
module Module1
  class Class1
  ...

obj = Module1::Class1.new
```


# 定数

サポートしています。

# グローバル変数

サポートしています。

# 多重代入

基本的な構文は動きます。
```
a,b = 1,2
a,b = [1,2,3]
a,*b = [1,2,3]
```

右辺を動的（コンパイル時に特定できない状態）に分離しなければならない場合で、左辺の前側にアスタリスクを付けるパターンのみ、唯一エラーになることがわかっています。使用頻度が非常に低いものとして判断し、サポートしていません。
```
ary = [1,2,3]
*a,b = ary     # Not supported
```

# 例外処理

v2.1 からサポートしています。
ただし、rescue, ensure, retry を複雑にからめて使うと、CRubyと非互換の動作をする場合があるようです。これは、mrubyコンパイラ側と協調して今後修正します。

## 例外の元となった例外

サポートしていません。
```
rescue => ex
  ex.cause     # Not supported
```


# メソッド定義

## メソッドの引数の数

255個までです。
ただし、MAX_REGS_SIZE プリプロセッサ定数に上限を制限されます。


## 省略可能な仮引数

サポートしています。
```
def greet(name, message = "Hi")
```

## 可変長引数

サポートしています。
```
def greet(name, *messages)
```

## 第２通常パラメータ

この例のa2です。サポートしていません。
```
def func1(a1,b=22,*c,a2)
```

## キーワード引数

v3.2からサポートしています。

## ブロック

v2.1 からサポートしています。

## 別名 (alias)

サポートしています。

## メソッドオーバライド

サポートしています。

## 特異メソッド

サポートしていません。


# 組み込みクラス

mruby/c の組み込みクラスです。
各クラスは、CRuby, mruby のクラスが持っているメソッド全てを実装しているわけではありません。また、可能な限り CRuby, mruby とメソッドの仕様を合致させるように努力していますが、完全互換にできていないものもあります。
ここでは、互換性に注意が必要な箇所に的を絞って解説します。

## クラスツリー
```
Object
  NilClass
  TrueClass
  FalseClass
  Integer
  Float
  String
  Math
  Proc
  Array
  Hash
  Range
  Exception
    NoMemoryError
    NotImplementedError
    StandardError
      ArgumentError
      IndexError
      IOError
      NameError
        NoMethodError
      RangeError
      RuntimeError
      TypeError
      ZeroDivisionError
```

## Integer

C言語の整数型と対応づけられた型で、コンパイル時に指定される MRBC_INT** プリプロセッサマクロによって、16bit, 32bit, 64bit のサイズが定義できます。C言語の整数型そのままの性質を受け継ぐため、オーバーフローやアンダーフローに対する保護はありません。

## Float

C言語の浮動小数点型と対応づけられた型で、通常は倍精度小数点 (double) 型です。

## String

エンコーディングの概念は無く、Ruby1.8互換とも言えます。

## Hash

実装は Array クラスと共用しており、速度的には Hash ではありません ( O(n) ) 。
mruby/c では、ハッシュテーブルが必要になるほどオンメモリで多くのデータを扱うことは無いだろうという判断です。

### each pair

Hash#each で以下の例のようにペアを受け取るコードは、現リリース全バージョンで意図通り動作しませんが、master では（動作速度と引き換えに）修正されています。
```
{a:1, b:2, c:3}.each do |pair|
  p pair
end
```


## Range

数値を使った通常の、`(1..3)` のようなオブジェクトであれば、全く問題なく使用できます。
`(1..nil)` といった nil 終端は、Rangeというよりも Range を利用するクラスが十分対応していない可能性があります。
`('A'..'Z')` といった数値以外のレンジは、オブジェクトを作ることは問題なくできますが、内包するオブジェクトが対応していないことによって意図した結果が得られない可能性が高いです。


# クロージャ

不完全なので、意図的にクロージャを使ったプログラミングを行う事はできません。

```
def create_proc
  str = "from create proc"
  Proc.new { str }
end

p create_proc.call      # Error. 実行時保護もまだ不十分なので、そのうち頑張る
```

これは、mruby/c の省メモリ戦略の一つである「実行時コンテキストを保存せず callinfo で代用する」という根幹の部分からくる制限ですので、おそらく今後も完全なクロージャを実装できることは無いと思われます。

(2025/01/31追加)
`Proc.new` が使えないというわけではありません。例えば上記プログラムも、このように書き換えれば動きます。
```
def create_proc
  Proc.new { "from create proc" }
end

p create_proc.call
```


# 同時実行機能

簡易なスケジューラを同梱しており、複数の Ruby プログラムを同時にタイムスライスで実行することができます。
see) [sample_c/sample_concurrent.c](https://github.com/mrubyc/mrubyc/blob/release3.3.1/sample_c/sample_concurrent.c)

## データ共有

グローバル変数は、全てのプログラムで共有しており、これを使ってプログラム間でデータ共有できます。

## 排他機能

Mutex クラスを持っており、それを使います。

```
$mutex1 = Mutex.new
$mutex1.lock
...
$mutex1.unlock
```


# その他の制限

その他、mruby/c にはできない事を落ち穂拾いします。

## 大域脱出 (catch, throw)

mruby, mruby/c とも、サポートしていません。

## 後判定ループ

mruby, mruby/c とも、サポートしていません。

```
i = 10
begin
  puts i += 1
end while i < 3
```

## require

mruby, mruby/c とも、サポートしていません。

（参考）
mrubyは mruby-require gem を導入すれば、その仕様の範囲内でサポートされます。
また、mruby/c VM を利用している [PicoRuby](https://github.com/picoruby/picoruby) も、require の仕組みを持っています。


## undef

サポートしていません。

## public, private, protected

サポートしていません。


## C言語で書いたメソッドから、Rubyで書かれたメソッドを呼ぶ

できません。（できるようにしたいとは考えています）

よって、ユーザークラスの to_s, inspect メソッドなどが、以下のような場合に動作しません。

```
class MyClass
  def to_s()
    return "MyClass#to_s called."
  end
end

obj1 = MyClass.new()
puts "obj1: #{obj1}"
```

CRuby実行結果
```
obj1: MyClass#to_s called.
```

mruby/c実行結果
```
obj1:
```


## 意図しない（と思われる）関数的メソッドの呼び出し

以下のコードでは、mruby, mruby/c とも、意図せず func1 が呼び出されます。これは public, private, protected が未サポートであることによって、意図しない呼び出しが行われます。

```
def func1
  p "func1 called."
end

class Class1
end

Class1.new.func1   # => "func1 called." と表示
```
(参考）https://hasumikin.com/2024/12/17/private-method-in-ruby.html


# おわりに

書き連ねてみると、できない事がとても多いように感じられますが、実際には Rubyらしいプログラムがいくつも mruby/c を使って書かれています。繰り返しになりますが、ここに書いた仕様・制限は、今日(2024/12/11)現在のものですので、今後リクエストなどいただければ、実装もしくは制限が緩和されるかもしれません。一方、それによって実行時のメモリ使用量を十分小さく保つという最も重要な設計思想が守られなくなると本末転倒ですので、そういった場合は mruby のVMを使うという方向にシフトしてもらう方が良いと思います。