Skip to content
Merged

Dev #388

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Changelog

## Unreleased
## 4.11.0 (Unreleased)

Incompatible Changes:
* `InstanceRegistry.isEnabled` (boolean) has been replaced by an `InstanceRegistry.isEnabled` which is an enum (`Off`, `Owned`, `All`).
* `InstanceRegistry` now defaults to `Owned` - previously it was disabled. The goal of this change is to ensure C++ objects owned by Ruby are only wrapped once to avoid double free errors.
* `Object()` now defaults to `Qnil` instead of `rb_cObject`. This avoids accidentally manipulating `rb_cObject` when a wrapper is not explicitly initialized. Calling methods on wrappers that point to `Qnil` will generally raise an exception. Use `object.is_nil()` to check for `nil` before using a wrapper as a receiver.
* C++ API wrappers now store their Ruby `VALUE` in a `Pin` instead of a raw `VALUE` field. Previously, wrappers were not GC-safe and Ruby could reclaim wrapped objects while C++ still referenced them. This is now fixed, and wrappers such as `Object` can be stored safely in containers like `std::vector`.
* The global `Object` constants (`Rice::Nil`, `Rice::True`, `Rice::False`, `Rice::Undef`) have been removed.
* `Object::test()` has been removed. Use `operator bool()` or `is_nil()` depending on the desired semantics.

## 4.10.0 (2026-02-07)

Expand Down
6 changes: 4 additions & 2 deletions docs/cpp_api/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@

### Class()

Construct a Class wrapping `rb_cObject`.
Default-construct an empty Class wrapper (wraps `Qnil`).

```cpp
Class c; // wraps Object class
Class c; // wraps nil
```

This constructor does not bind the wrapper to a Ruby class. Initialize it before calling class APIs.

---

### Class(VALUE v)
Expand Down
6 changes: 4 additions & 2 deletions docs/cpp_api/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@

### Module()

Construct a Module wrapping `rb_cObject`.
Default-construct an empty Module wrapper (wraps `Qnil`).

```cpp
Module m; // wraps Object
Module m; // wraps nil
```

This constructor does not bind the wrapper to a Ruby module. Initialize it before calling module APIs.

---

### Module(VALUE v)
Expand Down
25 changes: 10 additions & 15 deletions docs/cpp_api/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

`Rice::Object` is the base class for all Rice wrapper classes. It wraps a Ruby `VALUE` and provides a C++-style interface to Ruby's object system.

Note: `Object` stores its wrapped Ruby value using an internal `Pin`, so wrapper instances keep their Ruby `VALUE` protected from GC while the wrapper is alive. This makes long-lived C++ wrappers (including wrappers stored in STL containers) GC-safe.

---

## Constructors
Expand All @@ -18,6 +20,8 @@ Construct a new Object wrapping `Qnil`.
Object obj; // wraps nil
```

This is an intentionally "empty/nil" default state. It avoids accidentally targeting `rb_cObject` when a wrapper is default-constructed and later used without explicit initialization.

---

### Object(VALUE value)
Expand Down Expand Up @@ -63,29 +67,18 @@ Object obj(some_value);
VALUE v = obj.value();
```

If the wrapper does not contain a usable receiver, Rice will raise an exception when the value is used by APIs that require a receiver. Check `is_nil()` before calling receiver-style methods.

---

### test() const → bool
### operator bool() const

Test if the object is truthy.
Implicit conversion to bool.

**Returns:**

`false` if the object is `nil` or `false`; `true` otherwise.

```cpp
Object obj(Qtrue);
if (obj.test()) {
// ...
}
```

---

### operator bool() const

Implicit conversion to bool. Same as `test()`.

```cpp
Object obj(some_value);
if (obj) {
Expand All @@ -110,6 +103,8 @@ if (obj.is_nil()) {
}
```

Use this to guard receiver-style operations when a wrapper may be empty/nil.

---

### class_of() const → Class
Expand Down
236 changes: 236 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,241 @@
# Migration

## Version 4.10 to 4.11

Version 4.11 introduces several breaking changes in instance tracking and the C++ API wrappers.

### InstanceRegistry setting changed from boolean to mode enum

`InstanceRegistry.isEnabled` no longer takes/returns a boolean-style state. It now uses a mode enum:
`Off`, `Owned`, `All`.

Before:

```ruby
Rice::InstanceRegistry.isEnabled = true
Rice::InstanceRegistry.isEnabled = false
```

After:

```ruby
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::Owned
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::Off
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::All
```

Also note the default changed from disabled to `Owned`. If your code depended on registry-off behavior, set it explicitly to `Off`.

### `Object()` now defaults to `Qnil` (not `rb_cObject`)

Default-constructed wrappers are now nil wrappers.

Before:

```cpp
Object obj;
obj.call("some_method"); // could accidentally target rb_cObject
```

After:

```cpp
Object obj;
if (!obj.is_nil())
{
obj.call("some_method");
}
```

Or initialize explicitly:

```cpp
Object obj(rb_cObject);
obj.call("some_method");
```

### `Object::test()` removed

Replace calls to `test()` with either `operator bool()` or `is_nil()`, depending on intent.

Before:

```cpp
if (obj.test())
{
// truthy
}
```

After:

```cpp
if (obj)
{
// truthy
}
```

Or, if you specifically need nil checks:

```cpp
if (obj.is_nil())
{
// nil
}
```

### Global `Object` constants removed

The convenience globals were removed:
`Rice::Nil`, `Rice::True`, `Rice::False`, `Rice::Undef`.

Before:

```cpp
Object value = Rice::Nil;
```

After:

```cpp
Object value(Qnil);
Object truthy(Qtrue);
Object falsy(Qfalse);
Object undef_value(Qundef);
```

### C++ API wrappers now use `Pin` internally

Wrapper classes (such as `Object`) now pin their wrapped Ruby value internally for GC safety.

Most users do not need code changes for this update. The primary behavior change is improved safety for long-lived wrappers, including wrappers stored in containers:

```cpp
std::vector<Object> values;
values.push_back(Object(rb_str_new_cstr("hello")));
```

If you previously added ad-hoc GC guards only to protect wrapper objects, those guards may no longer be necessary for the wrappers themselves.

## Version 4.7 to 4.10

Versions 4.8, 4.9, and 4.10 introduced several incompatible C++ API changes.

### Buffer/GVL API renames (4.8)

Before:

```cpp
define_method("read", &MyClass::read, Arg("buffer").isBuffer());
define_method("write", &MyClass::write, Return().isBuffer());
define_method("compute", &MyClass::compute, Function().noGVL());
```

After:

```cpp
define_method("read", &MyClass::read, ArgBuffer("buffer"));
define_method("write", &MyClass::write, ReturnBuffer());
define_method("compute", &MyClass::compute, NoGvL());
```

### `is_convertible` return type changed (4.8)

`From_Ruby<T>::is_convertible` must return `double` instead of `Convertible`.

Before:

```cpp
Convertible is_convertible(VALUE value)
{
return Convertible::Exact;
}
```

After:

```cpp
double is_convertible(VALUE value)
{
return Convertible::Exact;
}
```

### Method/default argument verification is stricter (4.8)

Rice now validates default arguments and type registrations more aggressively. Code that previously compiled but had mismatched/default argument types may now raise errors during binding setup.

Migration step:
1. Ensure every default argument value matches the bound C++ parameter type.
2. Ensure all custom/opaque types are registered before using them in defaults.

### Smart pointer wrapper internals changed (4.9)

If you implemented custom Rice-side smart pointer wrappers, update them to the current `Std::SharedPtr<T>` / `Std::UniquePtr<T>` model and forwarding behavior.

Most users who only consume smart pointers through `define_method`/`define_constructor` do not need code changes.

### `Address_Registration_Guard` replaced by `Pin` (4.10)

Before:

```cpp
VALUE value_;
Address_Registration_Guard guard_;

MyClass()
: value_(rb_str_new2("test")), guard_(&value_)
{
}
```

After:

```cpp
Pin pin_;

MyClass()
: pin_(rb_str_new2("test"))
{
}

VALUE value() const
{
return pin_.value();
}
```

### Blocks are converted to Procs in C++ bindings (4.10)

If your C++ method expects a Ruby block, explicitly receive it as a `VALUE`/`Object` parameter and mark it as a value arg.

Before:

```cpp
define_method("run_with_block", [](VALUE self)
{
// expected implicit block handling
});
```

After:

```cpp
define_method("run_with_block", [](VALUE self, VALUE proc)
{
// proc is the Ruby block converted to Proc
}, Arg("proc").setValue());
```

### `Data_Type<T>::define()` removed (4.10)

Use class template binding helpers / explicit factory functions instead of `Data_Type<T>::define()`.

Migration step:
1. Remove `Data_Type<T>::define()` calls.
2. Replace with `define_class`/`define_class_under` flows documented in [Class Templates](bindings/class_templates.md).

## Version 4.6 to 4.7

Version 4.7 has a couple of breaking changes.
Expand Down
2 changes: 1 addition & 1 deletion rice/Data_Object.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ namespace Rice
template<typename T>
inline T* Data_Object<T>::get() const
{
if (this->value() == Qnil)
if (this->is_nil())
{
return nullptr;
}
Expand Down
3 changes: 1 addition & 2 deletions rice/Exception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ namespace Rice
VALUE value() const;

private:
// TODO: Do we need to tell the Ruby gc about an exception instance?
mutable VALUE exception_ = Qnil;
Pin exception_ = Qnil;
mutable std::string message_;
};
} // namespace Rice
Expand Down
Loading