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.
This document does not contain a complete list of limitations. Please help to improve it by submitting your findings.
Kernel.raise without arguments does not raise the current exception within
a rescue clause.
begin
1 / 0
rescue
raise
endZeroDivisionError is raised.
RuntimeError is raised instead of ZeroDivisionError. To re-raise the exception, you have to do:
begin
1 / 0
rescue => e
raise e
endmruby'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.
To reduce memory consumption Array does not support instance variables.
class Liste < Array
def initialize(str = nil)
@field = str
end
end
p Liste.new "foobar"[]
ArgumentError is raised.
The defined? keyword is considered too complex to be fully
implemented. It is recommended to use const_defined? and
other reflection methods instead.
defined?(Foo)nil
NameError is raised.
Aliasing a global variable works in CRuby but is not part of the ISO standard.
alias $a $__a__nil
Syntax error
Operators on some of the primitive classes cannot be overridden, as they are optimized in the VM.
class String
def +
end
end
'a' + 'b'ArgumentError is raised.
The re-defined + operator does not accept any arguments.
'ab'
Behavior of the operator wasn't changed.
Kernel#binding method requires the mruby-binding gem (included
in the metaprog gembox). Without this gem, binding is not
available.
Redefinition of nil? is ignored in conditional expressions.
a = "a"
def a.nil?
true
end
puts(a.nil? ? "truthy" : "falsy")Ruby outputs truthy. mruby outputs falsy.
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:
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.
def g(a: 1, b: a)
p [a,b]
end
g(a:1)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.
module M
def self.append_features(mod)
p :append
end
end
class C
include M
endPrints :append.
Nothing printed (since include does not call append_features internally).
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 is only partially supported in mruby. Currently, only the rightward assignment operator (=>) with simple variable binding is implemented.
expr => var # Supported: assigns expr to varFull pattern matching with case/in syntax and various pattern types:
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"
endOnly rightward assignment with simple variable binding:
[1, 2, 3] => x
puts x # => [1, 2, 3]The following are not supported:
case/insyntax- 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.
Module refinements (refine, using) are not supported in mruby.
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 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.
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.
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.
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[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)
[1,2,3][MyInt.new] # TypeError
"a" + MyStr.new # TypeError
a, b, c = MyAry.new # a=<MyAry obj>, 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.
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.
class SomeClass
def self.class_method
def nested; 'nested!'; end
end
end
SomeClass.class_methodSomeClass.nested # NoMethodError
SomeClass.new.nested # => "nested!" (instance method)
SomeClass.nested # => "nested!" (class method)
SomeClass.new.nested # NoMethodError
Writing nested def like this is unusual; this difference rarely
surfaces in practical code.
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.
def m(&b)
b.dup
end
x = m { break 1 }
x.callLocalJumpError # raised only after m returns; if called inside m,
# the dup is still a live block
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).
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.
Klass = Class.new
Klass.__send__(:initialize) {}TypeError: already initialized class
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.