# 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. ## `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. ## `Kernel#binding` is not supported without mruby-binding gem `Kernel#binding` method requires the `mruby-binding` gem (included in the `metaprog` gembox). Without this gem, `binding` is not available. ## `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.