Skip to content

Commit 306f078

Browse files
authored
Merge pull request #388 from ruby-rice/dev
Dev
2 parents 73bc0ee + 1b7c459 commit 306f078

42 files changed

Lines changed: 619 additions & 328 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# Changelog
22

3-
## Unreleased
3+
## 4.11.0 (Unreleased)
44

55
Incompatible Changes:
66
* `InstanceRegistry.isEnabled` (boolean) has been replaced by an `InstanceRegistry.isEnabled` which is an enum (`Off`, `Owned`, `All`).
77
* `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.
8+
* `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.
9+
* 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`.
10+
* The global `Object` constants (`Rice::Nil`, `Rice::True`, `Rice::False`, `Rice::Undef`) have been removed.
11+
* `Object::test()` has been removed. Use `operator bool()` or `is_nil()` depending on the desired semantics.
812

913
## 4.10.0 (2026-02-07)
1014

docs/cpp_api/class.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
### Class()
1414

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

1717
```cpp
18-
Class c; // wraps Object class
18+
Class c; // wraps nil
1919
```
2020

21+
This constructor does not bind the wrapper to a Ruby class. Initialize it before calling class APIs.
22+
2123
---
2224

2325
### Class(VALUE v)

docs/cpp_api/module.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
### Module()
1414

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

1717
```cpp
18-
Module m; // wraps Object
18+
Module m; // wraps nil
1919
```
2020

21+
This constructor does not bind the wrapper to a Ruby module. Initialize it before calling module APIs.
22+
2123
---
2224

2325
### Module(VALUE v)

docs/cpp_api/object.md

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
`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.
88

9+
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.
10+
911
---
1012

1113
## Constructors
@@ -18,6 +20,8 @@ Construct a new Object wrapping `Qnil`.
1820
Object obj; // wraps nil
1921
```
2022

23+
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.
24+
2125
---
2226

2327
### Object(VALUE value)
@@ -63,29 +67,18 @@ Object obj(some_value);
6367
VALUE v = obj.value();
6468
```
6569
70+
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.
71+
6672
---
6773
68-
### test() const → bool
74+
### operator bool() const
6975
70-
Test if the object is truthy.
76+
Implicit conversion to bool.
7177
7278
**Returns:**
7379
7480
`false` if the object is `nil` or `false`; `true` otherwise.
7581
76-
```cpp
77-
Object obj(Qtrue);
78-
if (obj.test()) {
79-
// ...
80-
}
81-
```
82-
83-
---
84-
85-
### operator bool() const
86-
87-
Implicit conversion to bool. Same as `test()`.
88-
8982
```cpp
9083
Object obj(some_value);
9184
if (obj) {
@@ -110,6 +103,8 @@ if (obj.is_nil()) {
110103
}
111104
```
112105

106+
Use this to guard receiver-style operations when a wrapper may be empty/nil.
107+
113108
---
114109

115110
### class_of() const → Class

docs/migration.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,241 @@
11
# Migration
22

3+
## Version 4.10 to 4.11
4+
5+
Version 4.11 introduces several breaking changes in instance tracking and the C++ API wrappers.
6+
7+
### InstanceRegistry setting changed from boolean to mode enum
8+
9+
`InstanceRegistry.isEnabled` no longer takes/returns a boolean-style state. It now uses a mode enum:
10+
`Off`, `Owned`, `All`.
11+
12+
Before:
13+
14+
```ruby
15+
Rice::InstanceRegistry.isEnabled = true
16+
Rice::InstanceRegistry.isEnabled = false
17+
```
18+
19+
After:
20+
21+
```ruby
22+
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::Owned
23+
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::Off
24+
Rice::InstanceRegistry.isEnabled = Rice::InstanceRegistry::All
25+
```
26+
27+
Also note the default changed from disabled to `Owned`. If your code depended on registry-off behavior, set it explicitly to `Off`.
28+
29+
### `Object()` now defaults to `Qnil` (not `rb_cObject`)
30+
31+
Default-constructed wrappers are now nil wrappers.
32+
33+
Before:
34+
35+
```cpp
36+
Object obj;
37+
obj.call("some_method"); // could accidentally target rb_cObject
38+
```
39+
40+
After:
41+
42+
```cpp
43+
Object obj;
44+
if (!obj.is_nil())
45+
{
46+
obj.call("some_method");
47+
}
48+
```
49+
50+
Or initialize explicitly:
51+
52+
```cpp
53+
Object obj(rb_cObject);
54+
obj.call("some_method");
55+
```
56+
57+
### `Object::test()` removed
58+
59+
Replace calls to `test()` with either `operator bool()` or `is_nil()`, depending on intent.
60+
61+
Before:
62+
63+
```cpp
64+
if (obj.test())
65+
{
66+
// truthy
67+
}
68+
```
69+
70+
After:
71+
72+
```cpp
73+
if (obj)
74+
{
75+
// truthy
76+
}
77+
```
78+
79+
Or, if you specifically need nil checks:
80+
81+
```cpp
82+
if (obj.is_nil())
83+
{
84+
// nil
85+
}
86+
```
87+
88+
### Global `Object` constants removed
89+
90+
The convenience globals were removed:
91+
`Rice::Nil`, `Rice::True`, `Rice::False`, `Rice::Undef`.
92+
93+
Before:
94+
95+
```cpp
96+
Object value = Rice::Nil;
97+
```
98+
99+
After:
100+
101+
```cpp
102+
Object value(Qnil);
103+
Object truthy(Qtrue);
104+
Object falsy(Qfalse);
105+
Object undef_value(Qundef);
106+
```
107+
108+
### C++ API wrappers now use `Pin` internally
109+
110+
Wrapper classes (such as `Object`) now pin their wrapped Ruby value internally for GC safety.
111+
112+
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:
113+
114+
```cpp
115+
std::vector<Object> values;
116+
values.push_back(Object(rb_str_new_cstr("hello")));
117+
```
118+
119+
If you previously added ad-hoc GC guards only to protect wrapper objects, those guards may no longer be necessary for the wrappers themselves.
120+
121+
## Version 4.7 to 4.10
122+
123+
Versions 4.8, 4.9, and 4.10 introduced several incompatible C++ API changes.
124+
125+
### Buffer/GVL API renames (4.8)
126+
127+
Before:
128+
129+
```cpp
130+
define_method("read", &MyClass::read, Arg("buffer").isBuffer());
131+
define_method("write", &MyClass::write, Return().isBuffer());
132+
define_method("compute", &MyClass::compute, Function().noGVL());
133+
```
134+
135+
After:
136+
137+
```cpp
138+
define_method("read", &MyClass::read, ArgBuffer("buffer"));
139+
define_method("write", &MyClass::write, ReturnBuffer());
140+
define_method("compute", &MyClass::compute, NoGvL());
141+
```
142+
143+
### `is_convertible` return type changed (4.8)
144+
145+
`From_Ruby<T>::is_convertible` must return `double` instead of `Convertible`.
146+
147+
Before:
148+
149+
```cpp
150+
Convertible is_convertible(VALUE value)
151+
{
152+
return Convertible::Exact;
153+
}
154+
```
155+
156+
After:
157+
158+
```cpp
159+
double is_convertible(VALUE value)
160+
{
161+
return Convertible::Exact;
162+
}
163+
```
164+
165+
### Method/default argument verification is stricter (4.8)
166+
167+
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.
168+
169+
Migration step:
170+
1. Ensure every default argument value matches the bound C++ parameter type.
171+
2. Ensure all custom/opaque types are registered before using them in defaults.
172+
173+
### Smart pointer wrapper internals changed (4.9)
174+
175+
If you implemented custom Rice-side smart pointer wrappers, update them to the current `Std::SharedPtr<T>` / `Std::UniquePtr<T>` model and forwarding behavior.
176+
177+
Most users who only consume smart pointers through `define_method`/`define_constructor` do not need code changes.
178+
179+
### `Address_Registration_Guard` replaced by `Pin` (4.10)
180+
181+
Before:
182+
183+
```cpp
184+
VALUE value_;
185+
Address_Registration_Guard guard_;
186+
187+
MyClass()
188+
: value_(rb_str_new2("test")), guard_(&value_)
189+
{
190+
}
191+
```
192+
193+
After:
194+
195+
```cpp
196+
Pin pin_;
197+
198+
MyClass()
199+
: pin_(rb_str_new2("test"))
200+
{
201+
}
202+
203+
VALUE value() const
204+
{
205+
return pin_.value();
206+
}
207+
```
208+
209+
### Blocks are converted to Procs in C++ bindings (4.10)
210+
211+
If your C++ method expects a Ruby block, explicitly receive it as a `VALUE`/`Object` parameter and mark it as a value arg.
212+
213+
Before:
214+
215+
```cpp
216+
define_method("run_with_block", [](VALUE self)
217+
{
218+
// expected implicit block handling
219+
});
220+
```
221+
222+
After:
223+
224+
```cpp
225+
define_method("run_with_block", [](VALUE self, VALUE proc)
226+
{
227+
// proc is the Ruby block converted to Proc
228+
}, Arg("proc").setValue());
229+
```
230+
231+
### `Data_Type<T>::define()` removed (4.10)
232+
233+
Use class template binding helpers / explicit factory functions instead of `Data_Type<T>::define()`.
234+
235+
Migration step:
236+
1. Remove `Data_Type<T>::define()` calls.
237+
2. Replace with `define_class`/`define_class_under` flows documented in [Class Templates](bindings/class_templates.md).
238+
3239
## Version 4.6 to 4.7
4240

5241
Version 4.7 has a couple of breaking changes.

rice/Data_Object.ipp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ namespace Rice
8484
template<typename T>
8585
inline T* Data_Object<T>::get() const
8686
{
87-
if (this->value() == Qnil)
87+
if (this->is_nil())
8888
{
8989
return nullptr;
9090
}

rice/Exception.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ namespace Rice
5858
VALUE value() const;
5959

6060
private:
61-
// TODO: Do we need to tell the Ruby gc about an exception instance?
62-
mutable VALUE exception_ = Qnil;
61+
Pin exception_ = Qnil;
6362
mutable std::string message_;
6463
};
6564
} // namespace Rice

0 commit comments

Comments
 (0)