Files
mruby-mruby/doc/limitations.md
Yukihiro "Matz" Matsumoto f5a5bfcbf6 limitations.md: replace binding note with general mrbgem pattern
The Kernel#binding entry was treating "feature provided by mrbgem"
as a per-method limitation, which does not scale. mruby implements
much of Ruby's standard surface area through mrbgems, so listing
each one would grow without bound.

Replace it with a single top-level note explaining the general
pattern: which features are available depends on the linked gems,
and a NoMethodError on a familiar Ruby method usually points to a
missing gem rather than a true mruby gap.

Refs #6861.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 08:49:07 +09:00

452 lines
11 KiB
Markdown

<!-- summary: About the Limitations of mruby -->
# 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=<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.
## 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.