# Limitations and Differences The philosophy of mruby is to be a lightweight implementation of the Ruby ISO standard. These two objectives are partially contradicting. Ruby is an expressive language with complex implementation details which are difficult to implement in a lightweight manner. To cope with this, limitations to the "Ruby Compatibility" are defined. This document is collecting these limitations. ## Integrity This document does not contain a complete list of limitations. Please help to improve it by submitting your findings. ## Features provided by mrbgems Many Ruby features that CRuby builds into its core are provided by mrbgems in mruby. Which features are actually available depends on which mrbgems are linked into the build. The `default.gembox` and `stdlib.gembox` cover the common cases, but a minimal build can omit familiar features such as `Kernel#binding` (provided by `mruby-binding`), `Kernel#catch`/`throw` (by `mruby-catch`), `Enumerable` extensions, `Comparable`, IO, regular expressions, and many more. This is by design rather than a limitation per se. When porting Ruby code to mruby, a `NoMethodError` or `NameError` often means "the gem providing this feature is not linked in" rather than "mruby does not support it." Adding the relevant gem to the build configuration is usually enough. ## `Kernel.raise` in rescue clause `Kernel.raise` without arguments does not raise the current exception within a rescue clause. ```ruby begin 1 / 0 rescue raise end ``` #### CRuby `ZeroDivisionError` is raised. #### mruby `RuntimeError` is raised instead of `ZeroDivisionError`. To re-raise the exception, you have to do: ```ruby begin 1 / 0 rescue => e raise e end ``` ## Fiber execution can't cross C function boundary mruby's `Fiber` is implemented similarly to Lua's co-routine. This results in the consequence that you can't switch context within C functions. Only exception is `mrb_fiber_yield` at return. ## `Array` does not support instance variables To reduce memory consumption `Array` does not support instance variables. ```ruby class Liste < Array def initialize(str = nil) @field = str end end p Liste.new "foobar" ``` #### CRuby `[]` #### mruby `ArgumentError` is raised. ## `defined?` The `defined?` keyword is considered too complex to be fully implemented. It is recommended to use `const_defined?` and other reflection methods instead. ```ruby defined?(Foo) ``` #### CRuby ``` nil ``` #### mruby `NameError` is raised. ## `alias` on global variables Aliasing a global variable works in CRuby but is not part of the ISO standard. ```ruby alias $a $__a__ ``` #### CRuby `nil` #### mruby Syntax error ## Operator modification Operators on some of the primitive classes cannot be overridden, as they are optimized in the VM. ```ruby class String def + end end 'a' + 'b' ``` #### CRuby `ArgumentError` is raised. The re-defined `+` operator does not accept any arguments. #### mruby `'ab'` Behavior of the operator wasn't changed. ## `nil?` redefinition in conditional expressions Redefinition of `nil?` is ignored in conditional expressions. ```ruby a = "a" def a.nil? true end puts(a.nil? ? "truthy" : "falsy") ``` Ruby outputs `truthy`. mruby outputs `falsy`. ## Argument Destructuring ```ruby def m(a,(b,c),d); p [a,b,c,d]; end m(1,[2,3],4) # => [1,2,3,4] ``` Destructured arguments (`b` and `c` in above example) cannot be accessed from the default expression of optional arguments and keyword arguments, since actual assignment is done after the evaluation of those default expressions. Thus: ```ruby def f(a,(b,c),d=b) p [a,b,c,d] end f(1,[2,3]) ``` CRuby gives `[1,2,3,nil]`. mruby raises `NoMethodError` for `b`. Keyword argument expansion has similar restrictions. The following example, gives `[1, 1]` for CRuby, mruby raises `NoMethodError` for `b`. ```ruby def g(a: 1, b: a) p [a,b] end g(a:1) ``` ## No Double Dispatch in Module Loading To make implementation simpler, mruby does not use double dispatching in module loading (`include`/`prepend`/`extend`). Those method internally called corresponding actual load methods (`append_features`/`prepend_features`/`extend_object`). But they are rarely overloaded, consumes more memory, and make loading little bit slower. As a Ruby implementation for the smaller device, we decided mruby simpler. ```ruby module M def self.append_features(mod) p :append end end class C include M end ``` #### CRuby Prints `:append`. #### mruby Nothing printed (since `include` does not call `append_features` internally). ## No `#hash` call for small hashes For performance reasons, mruby avoids calling the `#hash` method on keys when a hash table is small. This means that custom `#hash` methods on key objects may not be executed. ## Pattern Matching Pattern matching is only partially supported in mruby. Currently, only the rightward assignment operator (`=>`) with simple variable binding is implemented. ```ruby expr => var # Supported: assigns expr to var ``` #### CRuby Full pattern matching with `case/in` syntax and various pattern types: ```ruby case [1, 2, 3] in [a, b, c] puts "#{a}, #{b}, #{c}" # => "1, 2, 3" end case {name: "Alice", age: 30} in {name:, age:} puts "#{name} is #{age}" # => "Alice is 30" end ``` #### mruby Only rightward assignment with simple variable binding: ```ruby [1, 2, 3] => x puts x # => [1, 2, 3] ``` The following are **not supported**: - `case/in` syntax - Array patterns: `in [a, b, c]` - Hash patterns: `in {name:, age:}` - Guard clauses: `in pattern if condition` - Pin operator: `in ^variable` - Find patterns: `in [*, x, *]` - Alternative patterns: `in pattern1 | pattern2` - Boolean pattern check: `value in pattern` Note: mruby does provide `Array#deconstruct` and `Hash#deconstruct_keys` methods for future pattern matching compatibility. ## No Refinements Module refinements (`refine`, `using`) are not supported in mruby. ## No `Encoding` Class mruby does not have an `Encoding` class. Strings are treated as byte sequences by default. UTF-8 aware string operations can be enabled with the `MRB_UTF8_STRING` compile flag. ## Integer Precision Varies by Boxing Mode Integer size depends on the value boxing configuration: | Configuration | Integer range | | ----------------------------- | ---------------- | | Word boxing, 64-bit (default) | roughly +/- 2^62 | | Word boxing, 32-bit (default) | roughly +/- 2^30 | | NaN boxing (64-bit only) | -2^31 to 2^31-1 | Code relying on 64-bit integer precision may behave differently across configurations. The `mruby-bigint` gem provides arbitrary-precision integers when included. ## No `ObjectSpace.each_object` by Default `ObjectSpace` is only available via the `mruby-objectspace` gem (included in the `stdlib` gembox). Even with the gem, `ObjectSpace.each_object` has limited functionality compared to CRuby. ## No Implicit Type Conversion (`to_int`, `to_str`, `to_ary`, ...) mruby does not perform implicit type conversion through methods like `to_int`, `to_str`, `to_ary`, or `to_hash`. CRuby uses these to let user-defined classes duck-type as built-in types — for example `Array#[]` calls `to_int` on its argument, `String#+` calls `to_str`, and multiple assignment calls `to_ary` on its right-hand side. mruby's built-in operations require the actual built-in type and do not consult these conversion methods. ```ruby class MyInt; def to_int; 42; end; end class MyStr; def to_str; "x"; end; end class MyAry; def to_ary; [1,2,3]; end; end ``` #### CRuby ``` [1,2,3][MyInt.new] # => nil (to_int called -> ary[42]) "a" + MyStr.new # => "ax" (to_str called) a, b, c = MyAry.new # => a=1, b=2, c=3 (to_ary called) ``` #### mruby ``` [1,2,3][MyInt.new] # TypeError "a" + MyStr.new # TypeError a, b, c = MyAry.new # a=, b=nil, c=nil (treated as single value) ``` Identity versions of `to_int`, `to_str`, `to_sym`, and `to_hash` remain defined on the corresponding built-in types so that `respond_to?(:to_str)`-style checks work for built-in instances. `Float#to_int` and `Array#to_ary` are intentionally not defined. Explicit conversion methods (`to_i`, `to_s`, `to_a`) work as in CRuby and are called by features such as string interpolation and the splat operator (`*obj`). This is a deliberate trade-off: implicit conversion forces every coercion site to go through method dispatch and can silently mask type-mismatch bugs. ## Nested `def` in Singleton-Method Context `def` written inside a singleton method (`def self.foo`) is placed on a different class in mruby than in CRuby. CRuby registers the inner method as an instance method of the lexical enclosing class. mruby registers it as a method of the enclosing receiver's singleton class, which makes it visible as a class method of the enclosing class. ```ruby class SomeClass def self.class_method def nested; 'nested!'; end end end SomeClass.class_method ``` #### CRuby ``` SomeClass.nested # NoMethodError SomeClass.new.nested # => "nested!" (instance method) ``` #### mruby ``` SomeClass.nested # => "nested!" (class method) SomeClass.new.nested # NoMethodError ``` Writing nested `def` like this is unusual; this difference rarely surfaces in practical code. ## `Proc#dup` / `Proc#clone` is Always Orphan A `dup` or `clone` of a block given to a method is always treated as an orphan block in mruby — calling it raises `LocalJumpError` if the block contains `break` or `return`. CRuby is finer-grained: the copy inherits the orphan status of its original, so the copy only becomes orphan once the original yielding method returns. ```ruby def m(&b) b.dup end x = m { break 1 } x.call ``` #### CRuby ``` LocalJumpError # raised only after m returns; if called inside m, # the dup is still a live block ``` #### mruby ``` LocalJumpError # always raised — the dup is orphan from the moment # it is created ``` mruby's stricter rule keeps `RProc` from needing a back-pointer to the original block (which would also enlarge the GC mark set). ## `Class#initialize` Can Be Re-Invoked CRuby raises `TypeError: already initialized class` when `initialize` is invoked on a class that has already been set up. mruby's `Class#initialize` has no such guard — invoking it on an existing class through `__send__`, `send`, or `UnboundMethod#bind_call` silently succeeds. The superclass argument is ignored in this case, so the call cannot rewrite the class hierarchy; only the block (if any) is evaluated with the class as receiver. ```ruby Klass = Class.new Klass.__send__(:initialize) {} ``` #### CRuby ``` TypeError: already initialized class ``` #### mruby ``` The block is evaluated in the context of Klass; no error is raised. The superclass is not changed even when one is passed as an argument. ``` `Module#initialize` is re-callable in both implementations, so this divergence is `Class`-specific. Adding the CRuby check would require an additional flag bit on every `RClass`; mruby leaves the bit unspent because no destructive side effects are possible through this path.