@@ -127,6 +127,42 @@ MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_val
127127mrb_func_t rescue, mrb_value r_data,
128128mrb_int len, struct RClass **classes);
129129130+/**
131+ * Calls `func` via `mrb_protect_error()` and then always executes the user block exactly once.
132+ * Even if a global jump (similar to a Ruby exception) occurs within `func`, the block will be executed,
133+ * and after the block's completion, the global jump will be re-thrown.
134+ *
135+ * By checking `mrb->exc != NULL` within the block, you can determine if a global jump occurred in `func`.
136+ *
137+ * If you want to suppress the global jump and continue processing, use `mrb_clear_error(mrb); break;`.
138+ *
139+ * - `mrb`: The mruby state reference
140+ * - `result_var`: Pre-defined mrb_value type variable (to receive `func`'s return value)
141+ * - `func`: Function to call (compatible with `mrb_protect_error_func`)
142+ * - `data`: User data to pass to `func`
143+ *
144+ * Example:
145+ *
146+ * mrb_value result;
147+ * MRB_ENSURE(mrb, result, body_func, userdata) {
148+ * // This block is always executed (equivalent to Ruby's ensure)
149+ *
150+ * if (mrb->exc) {
151+ * // Post-processing when an exception occurs
152+ * }
153+ *
154+ * // To ignore the global jump, use `mrb_clear_error(mrb); break;` here
155+ * }
156+ */
157+#define MRB_ENSURE(mrb, result_var, func, data) \
158+ for (mrb_bool MRB_UNIQNAME(_break_) = FALSE; \
159+ !MRB_UNIQNAME(_break_) && \
160+ (((result_var) = mrb_protect_error(mrb, func, data, &MRB_UNIQNAME(_break_))), \
161+ ((mrb)->exc = (MRB_UNIQNAME(_break_) ? mrb_obj_ptr((result_var)) : NULL)), \
162+ TRUE); \
163+ (void)(MRB_UNIQNAME(_break_) && (mrb)->jmp && (mrb_exc_raise(mrb, result_var), TRUE)), \
164+ MRB_UNIQNAME(_break_) = TRUE)
165+130166MRB_END_DECL
131167132168#endif /* MRUBY_ERROR_H */