mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
f5a5bfcbf6
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>
452 lines
11 KiB
Markdown
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.
|