Skip to content
Merged

Dev #380

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
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,26 @@ Enhancements:
Internal:
* Refactor type handling by merging `TypeMapper` into `TypeDetail` and simplifying class hierarchy

Incomptatible Changes:
Incompatible Changes:
* `Address_Registration_Guard` has been replaced by `Pin`. If you are using `Address_Registration_Guard`
to protect Ruby VALUEs from garbage collection, update your code to use `Pin` instead:

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 getValue() { return pin_.get(); }
```

`Pin` is self-contained and provides `get()` and `set()` methods to access the pinned VALUE.

* Rice converts Ruby blocks to procs. Thus if you have a method that expects a block, you must add
a VALUE parameter and tell Rice it is a value. For example:

Expand Down
25 changes: 16 additions & 9 deletions Rice.cmake
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
add_library(Rice INTERFACE)
add_library(Rice::Rice ALIAS Rice)
include_guard(GLOBAL)

target_include_directories(Rice INTERFACE
$<BUILD_INTERFACE:$<$<CONFIG:Debug>:${CMAKE_CURRENT_SOURCE_DIR}>>
$<BUILD_INTERFACE:$<$<CONFIG:Release>:${CMAKE_CURRENT_SOURCE_DIR}/include>>
$<INSTALL_INTERFACE:include>
)
if(NOT TARGET Rice)
add_library(Rice INTERFACE)

# Need C++17 or higher
target_compile_features(Rice INTERFACE cxx_std_17)
target_include_directories(Rice INTERFACE
$<BUILD_INTERFACE:$<$<CONFIG:Debug>:${CMAKE_CURRENT_SOURCE_DIR}>>
$<BUILD_INTERFACE:$<$<CONFIG:Release>:${CMAKE_CURRENT_SOURCE_DIR}/include>>
$<INSTALL_INTERFACE:include>
)

# Need C++17 or higher
target_compile_features(Rice INTERFACE cxx_std_17)
endif()

if(NOT TARGET Rice::Rice)
add_library(Rice::Rice ALIAS Rice)
endif()
11 changes: 11 additions & 0 deletions docs/bindings/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,14 @@ abort "libffi not found" unless have_libffi
```

If you are using CMake, you will need to add a C++ preprocessor define called `HAVE_LIBFFI` and link to libffi.

## Memory Management

Callback wrappers (NativeCallback instances) are intentionally never freed. This is because:

1. C code may call the callback at any time, even after the Ruby code that registered it has finished executing
2. Ruby users commonly pass blocks to C callbacks, which Rice converts to Procs internally. Without special handling, these Procs would be garbage collected and the callback would crash when invoked

Rice uses `Pin` internally to prevent the Ruby Proc from being garbage collected, ensuring the callback remains valid for the lifetime of the program.

In practice, this means each unique callback registration consumes a small amount of memory that is never reclaimed. For most applications this is not a concern, as callbacks are typically registered once during initialization.
44 changes: 43 additions & 1 deletion docs/bindings/memory_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,49 @@ If you are working with VALUEs or Objects stored on the stack, the Ruby garbage

### Heap

If you allocate an Object on the heap or if it is a member of an object that might be allocated on the heap, use `Rice::Address_Registration_Guard` to register the object with the garbage collector.
If a C++ object holds a Ruby VALUE and that C++ object is *not* wrapped by Ruby (i.e., it's allocated on the heap or is a standalone object), use `Rice::Pin` to prevent the garbage collector from collecting the Ruby object.

```cpp
class Container
{
public:
Container(VALUE value) : pin_(value) {}
VALUE getValue() const { return pin_.get(); }
void setValue(VALUE value) { pin_.set(value); }

private:
Rice::Pin pin_;
};
```

#### Pin API

* `Pin(VALUE value)` - Construct a Pin that protects the given VALUE from garbage collection
* `VALUE get() const` - Retrieve the pinned VALUE
* `void set(VALUE value)` - Replace the pinned VALUE

#### Copy Semantics

`Pin` uses shared ownership internally. When you copy a `Pin`, both copies share the same underlying GC anchor:

```cpp
Pin pin1(some_value);
Pin pin2 = pin1; // pin1 and pin2 share the same anchor

pin1.set(other_value); // Both pin1.get() and pin2.get() now return other_value
```

This is useful when multiple C++ objects need to reference the same Ruby object - only one GC registration is needed.

#### When to Use Pin vs ruby_mark

Use `Pin` when:
* The C++ object is **not** wrapped by Ruby (e.g., created with `new` in C++, stored in a global, or part of a C++ library's internal data structures)
* You want self-contained protection without manual GC registration

Use `ruby_mark` (see below) when:
* The C++ object **is** wrapped by Ruby via `Data_Type`
* Ruby owns the C++ object and will call the mark function during garbage collection

### Member Variables

Expand Down
Loading