diff --git a/src/block.rs b/src/block.rs
index 6280a110..09a1eb37 100644
--- a/src/block.rs
+++ b/src/block.rs
@@ -301,11 +301,7 @@ impl TryConvert for Proc {
 ///
 /// This effectivly makes the closure's lifetime managed by Ruby. It will be
 /// dropped when the returned `Value` is garbage collected.
-fn wrap_closure<F, R>(func: F) -> (*mut F, Value)
-where
-    F: FnMut(&[Value], Option<Proc>) -> R,
-    R: BlockReturn,
-{
+pub(crate) fn wrap_closure<F>(func: F) -> (*mut F, Value) {
     struct Closure();
     impl DataTypeFunctions for Closure {}
     let data_type = memoize!(DataType: {
diff --git a/src/lib.rs b/src/lib.rs
index 576a8b19..5992c867 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -693,12 +693,9 @@
 // * `rb_fd_term`:
 // * `rb_fd_zero`:
 // * `rb_feature_provided`:
-// * `rb_fiber_alive_p`:
-// * `rb_fiber_current`:
-// * `rb_fiber_new`:
-// * `rb_fiber_raise`:
-// * `rb_fiber_resume`:
 // * `rb_fiber_resume_kw`:
+// * `rb_fiber_transfer_kw`:
+// * `rb_fiber_yield_kw`:
 // * `rb_fiber_scheduler_address_resolve`:
 // * `rb_fiber_scheduler_block`:
 // * `rb_fiber_scheduler_close`:
@@ -723,10 +720,6 @@
 // * `rb_fiber_scheduler_process_wait`:
 // * `rb_fiber_scheduler_set`:
 // * `rb_fiber_scheduler_unblock`:
-// * `rb_fiber_transfer`:
-// * `rb_fiber_transfer_kw`:
-// * `rb_fiber_yield`:
-// * `rb_fiber_yield_kw`:
 //! * `rb_filesystem_encindex`: [`encoding::Index::filesystem`].
 //! * `rb_filesystem_encoding`:
 //!   [`RbEncoding::filesystem`](encoding::RbEncoding::filesystem).
@@ -796,7 +789,6 @@
 // * `rb_funcall_passing_block`:
 // * `rb_funcall_passing_block_kw`:
 //! * `rb_funcall_with_block`: [`Value::funcall_with_block`].
-// * `rb_funcall_with_block_kw`:
 // * `rb_f_abort`:
 // * `rb_f_exec`:
 // * `rb_f_exit`:
@@ -855,7 +847,6 @@
 //! # `rb_h`
 //!
 // * `rb_Hash`:
-// * `rb_hash`:
 //! * `rb_hash_aref`: [`RHash::aref`].
 //! * `rb_hash_aset`: [`RHash::aset`].
 // * `rb_hash_bulk_insert`:
@@ -928,7 +919,6 @@
 // * `rb_io_check_readable`:
 // * `rb_io_check_writable`:
 // * `rb_io_close`:
-// * `rb_io_descriptor`:
 // * `rb_io_eof`:
 // * `rb_io_extract_encoding_option`:
 // * `rb_io_extract_modeenc`:
@@ -1087,13 +1077,8 @@
 // * `rb_mod_sys_fail`:
 // * `rb_mod_sys_fail_str`:
 // * `rb_must_asciicompat`:
-// * `rb_mutex_lock`:
-// * `rb_mutex_locked_p`:
-// * `rb_mutex_new`:
 // * `rb_mutex_sleep`:
 // * `rb_mutex_synchronize`:
-// * `rb_mutex_trylock`:
-// * `rb_mutex_unlock`:
 //!
 //! ## `rb_n`
 //!
@@ -1183,9 +1168,7 @@
 // * `rb_obj_instance_eval`:
 // * `rb_obj_instance_exec`:
 // * `rb_obj_instance_variables`:
-// * `rb_obj_is_fiber`:
 // * `rb_obj_is_instance_of`:
-//! * `rb_obj_is_kind_of`: [`Value::is_kind_of`].
 // * `rb_obj_is_method`:
 //! * `rb_obj_is_proc`: [`Proc::from_value`](block::Proc::from_value).
 // * `rb_obj_method`:
@@ -1530,7 +1513,6 @@
 // * `rb_thread_current`:
 // * `rb_thread_fd_close`:
 // * `rb_thread_fd_select`:
-// * `rb_thread_fd_writable`:
 // * `rb_thread_interrupted`:
 // * `rb_thread_kill`:
 // * `rb_thread_local_aref`:
@@ -1544,7 +1526,6 @@
 // * `rb_thread_sleep_deadly`:
 // * `rb_thread_sleep_forever`:
 // * `rb_thread_stop`:
-// * `rb_thread_wait_fd`:
 // * `rb_thread_wait_for`:
 // * `rb_thread_wakeup`:
 // * `rb_thread_wakeup_alive`:
@@ -1821,15 +1802,20 @@ mod object;
 mod r_array;
 mod r_bignum;
 mod r_complex;
+#[cfg(ruby_gt_3_0)]
+pub mod r_fiber;
 mod r_file;
 mod r_float;
 pub mod r_hash;
+pub mod r_io;
 mod r_match;
+pub mod r_mutex;
 mod r_object;
 mod r_rational;
 mod r_regexp;
 pub mod r_string;
 pub mod r_struct;
+pub mod r_thread;
 pub mod r_typed_data;
 mod range;
 #[cfg(feature = "rb-sys-interop")]
diff --git a/src/object.rs b/src/object.rs
index a5a2cb1a..e4c89167 100644
--- a/src/object.rs
+++ b/src/object.rs
@@ -1,7 +1,8 @@
 use std::{ffi::CString, mem::transmute, ops::Deref};
 
 use crate::ruby_sys::{
-    rb_define_singleton_method, rb_extend_object, rb_ivar_get, rb_ivar_set, rb_singleton_class,
+    rb_define_singleton_method, rb_extend_object, rb_hash, rb_ivar_get, rb_ivar_set,
+    rb_singleton_class,
 };
 
 use crate::{
@@ -16,6 +17,13 @@ use crate::{
 
 /// Functions available all non-immediate values.
 pub trait Object: Deref<Target = Value> + Copy {
+    /// Call `obj.hash`
+    fn hash(self) -> Result<i64, Error> {
+        debug_assert_value!(self);
+        let hash = protect(|| Value::new(unsafe { rb_hash(self.as_rb_value()) }))?;
+        hash.try_convert()
+    }
+
     /// Define a singleton method in `self`'s scope.
     ///
     /// Singleton methods defined on a class are Ruby's method for implementing
diff --git a/src/r_fiber.rs b/src/r_fiber.rs
new file mode 100644
index 00000000..56d92864
--- /dev/null
+++ b/src/r_fiber.rs
@@ -0,0 +1,408 @@
+//!
+
+use {
+    crate::{
+        block::{wrap_closure, Proc},
+        define_class,
+        error::{protect, Error},
+        exception::type_error,
+        float::Float,
+        method::{Block, BlockReturn},
+        object::Object,
+        r_string::RString,
+        ruby_sys::{
+            rb_fiber_alive_p, rb_fiber_current, rb_fiber_new, rb_fiber_raise, rb_fiber_resume,
+            rb_fiber_scheduler_block, rb_fiber_scheduler_get, rb_fiber_scheduler_kernel_sleep,
+            rb_fiber_scheduler_set, rb_fiber_transfer, rb_fiber_yield, rb_obj_is_fiber, VALUE,
+        },
+        try_convert::{ArgList, TryConvert},
+        value::{private, NonZeroValue, ReprValue, Value},
+        ExceptionClass,
+    },
+    std::{
+        fmt::{Debug, Display, Error as FError, Formatter},
+        iter::once,
+        ops::Deref,
+        os::raw::c_int,
+        ptr::null,
+        time::Duration,
+    },
+};
+
+/// ::Fiber
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct RFiber(NonZeroValue);
+
+impl Deref for RFiber {
+    type Target = Value;
+    fn deref(&self) -> &Self::Target {
+        self.0.get_ref()
+    }
+}
+
+impl TryConvert for RFiber {
+    fn try_convert(val: Value) -> Result<Self, Error> {
+        Self::from_value(val).ok_or_else(|| {
+            Error::new(
+                type_error(),
+                format!("no implicit conversion of {} into Fiber", unsafe {
+                    val.classname()
+                }),
+            )
+        })
+    }
+}
+
+impl From<RFiber> for Value {
+    fn from(f: RFiber) -> Self {
+        *f
+    }
+}
+
+unsafe impl private::ReprValue for RFiber {
+    fn to_value(self) -> Value {
+        *self
+    }
+
+    unsafe fn from_value_unchecked(val: Value) -> Self {
+        Self(NonZeroValue::new_unchecked(val))
+    }
+}
+
+impl ReprValue for RFiber {}
+
+impl Object for RFiber {}
+
+impl Display for RFiber {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", unsafe { self.to_s_infallible() })
+    }
+}
+
+impl Debug for RFiber {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", self.inspect())
+    }
+}
+
+impl RFiber {
+    /// Return `Some(RFiber)` if `val` is a `RFiber`, `None` otherwise.
+    #[inline]
+    pub fn from_value(val: Value) -> Option<Self> {
+        unsafe {
+            Value::new(rb_obj_is_fiber(val.as_rb_value()))
+                .to_bool()
+                .then(|| Self(NonZeroValue::new_unchecked(val)))
+        }
+    }
+}
+
+impl RFiber {
+    /**
+     * Fiber.new
+     */
+    pub fn blocking<F, R>(block: F) -> Self
+    where
+        F: 'static + FnMut(&[Value], Option<Proc>) -> R,
+        R: BlockReturn,
+    {
+        unsafe extern "C" fn call<F, R>(
+            _yielded_arg: VALUE,
+            callback_arg: VALUE,
+            argc: c_int,
+            argv: *const VALUE,
+            blockarg: VALUE,
+        ) -> VALUE
+        where
+            F: FnMut(&[Value], Option<Proc>) -> R,
+            R: BlockReturn,
+        {
+            let closure = &mut *(callback_arg as *mut F);
+            Block::new(closure)
+                .call_handle_error(argc, argv as *const Value, Value::new(blockarg))
+                .as_rb_value()
+        }
+
+        let (closure, keepalive) = wrap_closure(block);
+        let call_func =
+            call::<F, R> as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE;
+        #[cfg(ruby_lt_2_7)]
+        let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) };
+
+        let fiber = Self(unsafe {
+            NonZeroValue::new_unchecked(Value::new(rb_fiber_new(Some(call_func), closure as VALUE)))
+        });
+        fiber.ivar_set("__rust_closure", keepalive).unwrap();
+        fiber
+    }
+
+    /**
+     * Fiber.new
+     */
+    pub fn non_blocking(block: Proc) -> Result<Self, Error> {
+        let fiber_class = define_class("Fiber", Default::default())?;
+        let fib = fiber_class.funcall_with_block_kw::<_, _, _, Value>(
+            "new",
+            (),
+            (("blocking", false),),
+            block,
+        )?;
+        Ok(Self(unsafe { NonZeroValue::new_unchecked(fib) }))
+    }
+
+    /**
+     * Fiber.current
+     */
+    pub fn current() -> Self {
+        Self(unsafe { NonZeroValue::new_unchecked(Value::new(rb_fiber_current())) })
+    }
+
+    /**
+     * Fiber.yield
+     */
+    pub fn rb_yield<A>(args: A) -> Result<Value, Error>
+    where
+        A: ArgList,
+    {
+        let args = args.into_arg_list();
+        let args = args.as_ref();
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_yield(args.len() as c_int, args.as_ptr() as *const VALUE)
+            })
+        })
+    }
+
+    /**
+     * fiber.alive?
+     */
+    pub fn alive(&self) -> bool {
+        unsafe { Value::new(rb_fiber_alive_p(self.as_rb_value())) }.to_bool()
+    }
+
+    /**
+     * fiber.resume
+     */
+    pub fn resume<A, R>(&self, args: A) -> Result<R, Error>
+    where
+        A: ArgList,
+        R: TryConvert,
+    {
+        let args = args.into_arg_list();
+        let args = args.as_ref();
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_resume(
+                    self.as_rb_value(),
+                    args.len() as c_int,
+                    args.as_ptr() as *const VALUE,
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+
+    /**
+     * fiber.transfer
+     */
+    pub fn transfer<A, R>(&self, args: A) -> Result<R, Error>
+    where
+        A: ArgList,
+        R: TryConvert,
+    {
+        let args = args.into_arg_list();
+        let args = args.as_ref();
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_transfer(
+                    self.as_rb_value(),
+                    args.len() as c_int,
+                    args.as_ptr() as *const VALUE,
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+
+    /**
+     * fiber.raise
+     */
+    pub fn raise<R>(&self) -> Result<R, Error>
+    where
+        R: TryConvert,
+    {
+        protect(|| {
+            Value::new(unsafe { rb_fiber_raise(self.as_rb_value(), Default::default(), null()) })
+        })
+        .and_then(R::try_convert)
+    }
+
+    /**
+     * fiber.raise
+     */
+    pub fn raise_with_message<M, R>(&self, msg: M) -> Result<R, Error>
+    where
+        M: Into<RString>,
+        R: TryConvert,
+    {
+        let args = [msg.into().as_rb_value()];
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_raise(
+                    self.as_rb_value(),
+                    args.len() as c_int,
+                    &args as *const VALUE,
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+
+    /**
+     * fiber.raise
+     */
+    pub fn raise_exn<E, A, R>(&self, exn: E, args: A) -> Result<R, Error>
+    where
+        E: Into<ExceptionClass>,
+        A: ArgList,
+        R: TryConvert,
+    {
+        let args = once(exn.into().as_rb_value())
+            .chain(
+                args.into_arg_list()
+                    .as_ref()
+                    .iter()
+                    .copied()
+                    .map(Value::as_rb_value),
+            )
+            .collect::<Vec<_>>();
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_raise(
+                    self.as_rb_value(),
+                    args.len() as c_int,
+                    args.as_ptr() as *const VALUE,
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+}
+
+/**
+ * An unknown fiber scheduler from ruby
+ */
+#[derive(Copy, Clone)]
+pub struct OpaqueScheduler(NonZeroValue);
+
+impl Deref for OpaqueScheduler {
+    type Target = Value;
+    fn deref(&self) -> &Self::Target {
+        self.0.get_ref()
+    }
+}
+
+impl From<OpaqueScheduler> for Value {
+    fn from(f: OpaqueScheduler) -> Self {
+        *f
+    }
+}
+
+impl Object for OpaqueScheduler {}
+
+impl OpaqueScheduler {
+    ///
+    /// ::Fiber.scheduler
+    ///
+    pub fn current_thread() -> Option<OpaqueScheduler> {
+        let value = Value::new(unsafe { rb_fiber_scheduler_get() });
+        if value.is_nil() {
+            None
+        } else {
+            Some(OpaqueScheduler(unsafe {
+                NonZeroValue::new_unchecked(value)
+            }))
+        }
+    }
+
+    ///
+    /// ::Fiber.set_scheduler
+    ///
+    pub fn set_current_thread<S>(scheduler: S) -> Result<(), Error>
+    where
+        S: Into<Value>,
+    {
+        let v = protect(|| {
+            let sch = scheduler.into().as_rb_value();
+            Value::new(unsafe { rb_fiber_scheduler_set(sch) })
+        })?;
+        debug_assert!(v.is_nil());
+        Ok(())
+    }
+
+    ///
+    /// scheduler.sleep
+    ///
+    pub fn sleep<D, R>(&self, duration: Option<Duration>) -> Result<R, Error>
+    where
+        R: TryConvert,
+    {
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_scheduler_kernel_sleep(
+                    self.as_rb_value(),
+                    duration
+                        .map::<Value, _>(|d| Float::from_f64(d.as_secs_f64()).into())
+                        .unwrap_or_default()
+                        .as_rb_value(),
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+
+    ///
+    /// scheduler.block
+    ///
+    pub fn block<B, R>(&self, blocker: B, timeout: Option<Duration>) -> Result<R, Error>
+    where
+        B: Into<Value>,
+        R: TryConvert,
+    {
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_scheduler_block(
+                    self.as_rb_value(),
+                    blocker.into().as_rb_value(),
+                    timeout
+                        .map::<Value, _>(|t| Float::from_f64(t.as_secs_f64()).into())
+                        .unwrap_or_default()
+                        .as_rb_value(),
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+
+    ///
+    /// scheduler.unblock
+    ///
+    pub fn unblock<B, F, R>(&self, blocker: B, fiber: F) -> Result<R, Error>
+    where
+        B: Into<Value>,
+        F: Into<RFiber>,
+        R: TryConvert,
+    {
+        protect(|| {
+            Value::new(unsafe {
+                rb_fiber_scheduler_block(
+                    self.as_rb_value(),
+                    blocker.into().as_rb_value(),
+                    fiber.into().as_rb_value(),
+                )
+            })
+        })
+        .and_then(R::try_convert)
+    }
+}
diff --git a/src/r_io.rs b/src/r_io.rs
new file mode 100644
index 00000000..f240ae35
--- /dev/null
+++ b/src/r_io.rs
@@ -0,0 +1,95 @@
+//!
+
+use {
+    crate::{
+        class::io,
+        error::Error,
+        exception::type_error,
+        object::Object,
+        try_convert::TryConvert,
+        value::{private, NonZeroValue, ReprValue, Value},
+    },
+    std::{
+        fmt::{Debug, Display, Error as FError, Formatter},
+        ops::Deref,
+    },
+};
+
+/// IO
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct RIO(NonZeroValue);
+
+impl Deref for RIO {
+    type Target = Value;
+    fn deref(&self) -> &Self::Target {
+        self.0.get_ref()
+    }
+}
+
+impl TryConvert for RIO {
+    fn try_convert(val: Value) -> Result<Self, Error> {
+        Self::from_value(val).ok_or_else(|| {
+            Error::new(
+                type_error(),
+                format!("no implicit conversion of {} into IO", unsafe {
+                    val.classname()
+                }),
+            )
+        })
+    }
+}
+
+impl From<RIO> for Value {
+    fn from(f: RIO) -> Self {
+        *f
+    }
+}
+
+unsafe impl private::ReprValue for RIO {
+    fn to_value(self) -> Value {
+        *self
+    }
+
+    unsafe fn from_value_unchecked(val: Value) -> Self {
+        Self(NonZeroValue::new_unchecked(val))
+    }
+}
+
+impl ReprValue for RIO {}
+
+impl Object for RIO {}
+
+impl Display for RIO {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", unsafe { self.to_s_infallible() })
+    }
+}
+
+impl Debug for RIO {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", self.inspect())
+    }
+}
+
+impl RIO {
+    /// Return `Some(RIO)` if `val` is a `::IO`, `None` otherwise.
+    #[inline]
+    pub fn from_value(val: Value) -> Option<Self> {
+        if val.is_kind_of(io()) {
+            Some(Self(unsafe { NonZeroValue::new_unchecked(val) }))
+        } else {
+            None
+        }
+    }
+}
+
+#[cfg(ruby_gt_3_0)]
+use {crate::ruby_sys::rb_io_descriptor, std::os::raw::c_int};
+#[cfg(ruby_gt_3_0)]
+impl RIO {
+    /// `io.fileno`
+    pub fn descriptor(&self) -> c_int {
+        unsafe { rb_io_descriptor(self.as_rb_value()) }
+    }
+}
diff --git a/src/r_mutex.rs b/src/r_mutex.rs
new file mode 100644
index 00000000..388eaf8b
--- /dev/null
+++ b/src/r_mutex.rs
@@ -0,0 +1,89 @@
+//!
+
+use {
+    crate::{
+        error::{protect, Error},
+        object::Object,
+        ruby_sys::{
+            rb_mutex_lock, rb_mutex_locked_p, rb_mutex_new, rb_mutex_trylock, rb_mutex_unlock,
+        },
+        value::{private, NonZeroValue, ReprValue, Value},
+    },
+    std::{
+        fmt::{Debug, Display, Error as FError, Formatter},
+        ops::Deref,
+    },
+};
+
+/// ::Thread::Mutex
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct RMutex(NonZeroValue);
+
+impl Deref for RMutex {
+    type Target = Value;
+    fn deref(&self) -> &Self::Target {
+        self.0.get_ref()
+    }
+}
+
+impl From<RMutex> for Value {
+    fn from(f: RMutex) -> Self {
+        *f
+    }
+}
+
+unsafe impl private::ReprValue for RMutex {
+    fn to_value(self) -> Value {
+        *self
+    }
+
+    unsafe fn from_value_unchecked(val: Value) -> Self {
+        Self(NonZeroValue::new_unchecked(val))
+    }
+}
+
+impl ReprValue for RMutex {}
+
+impl Object for RMutex {}
+
+impl Display for RMutex {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", unsafe { self.to_s_infallible() })
+    }
+}
+
+impl Debug for RMutex {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", self.inspect())
+    }
+}
+
+impl RMutex {
+    ///
+    pub fn new() -> Self {
+        Self(unsafe { NonZeroValue::new_unchecked(Value::new(rb_mutex_new())) })
+    }
+
+    ///
+    pub fn locked(&self) -> bool {
+        Value::new(unsafe { rb_mutex_locked_p(self.as_rb_value()) }).to_bool()
+    }
+
+    ///
+    pub fn try_lock(&self) -> bool {
+        Value::new(unsafe { rb_mutex_trylock(self.as_rb_value()) }).to_bool()
+    }
+
+    ///
+    pub fn lock(&self) -> Result<(), Error> {
+        protect(|| Value::new(unsafe { rb_mutex_lock(self.as_rb_value()) }))?;
+        Ok(())
+    }
+
+    ///
+    pub fn unlock(&self) -> Result<(), Error> {
+        protect(|| Value::new(unsafe { rb_mutex_unlock(self.as_rb_value()) }))?;
+        Ok(())
+    }
+}
diff --git a/src/r_thread.rs b/src/r_thread.rs
new file mode 100644
index 00000000..a3043a7f
--- /dev/null
+++ b/src/r_thread.rs
@@ -0,0 +1,145 @@
+//!
+
+use {
+    crate::{
+        block::wrap_closure,
+        class::thread,
+        error::{protect, raise, Error},
+        exception::type_error,
+        method::BlockReturn,
+        object::Object,
+        ruby_sys::{rb_thread_create, rb_thread_fd_writable, rb_thread_wait_fd, VALUE},
+        try_convert::TryConvert,
+        value::{private, NonZeroValue, ReprValue, Value},
+    },
+    std::{
+        ffi::c_void,
+        fmt::{Debug, Display, Error as FError, Formatter},
+        ops::Deref,
+        os::raw::c_int,
+        panic::{catch_unwind, AssertUnwindSafe},
+    },
+};
+
+/// ::Thread
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct RThread(NonZeroValue);
+
+impl Deref for RThread {
+    type Target = Value;
+    fn deref(&self) -> &Self::Target {
+        self.0.get_ref()
+    }
+}
+
+impl TryConvert for RThread {
+    fn try_convert(val: Value) -> Result<Self, Error> {
+        Self::from_value(val).ok_or_else(|| {
+            Error::new(
+                type_error(),
+                format!("no implicit conversion of {} into Thread", unsafe {
+                    val.classname()
+                }),
+            )
+        })
+    }
+}
+
+impl From<RThread> for Value {
+    fn from(f: RThread) -> Self {
+        *f
+    }
+}
+
+unsafe impl private::ReprValue for RThread {
+    fn to_value(self) -> Value {
+        *self
+    }
+
+    unsafe fn from_value_unchecked(val: Value) -> Self {
+        Self(NonZeroValue::new_unchecked(val))
+    }
+}
+
+impl ReprValue for RThread {}
+
+impl Object for RThread {}
+
+impl Display for RThread {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", unsafe { self.to_s_infallible() })
+    }
+}
+
+impl Debug for RThread {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FError> {
+        write!(f, "{}", self.inspect())
+    }
+}
+
+impl RThread {
+    /// Return `Some(RThread)` if `val` is a `::Thread`, `None` otherwise.
+    #[inline]
+    pub fn from_value(val: Value) -> Option<Self> {
+        if val.is_kind_of(thread()) {
+            Some(Self(unsafe { NonZeroValue::new_unchecked(val) }))
+        } else {
+            None
+        }
+    }
+}
+
+impl RThread {
+
+    #[cfg(ruby_gte_2_7)]
+    ///
+    pub fn from_fn<F, R>(f: F) -> Result<Self, Error>
+    where
+        F: 'static + FnMut() -> R + Send,
+        R: BlockReturn,
+    {
+        unsafe extern "C" fn call<F, R>(callback: *mut c_void) -> VALUE
+        where
+            F: FnMut() -> R,
+            R: BlockReturn,
+        {
+            match catch_unwind(AssertUnwindSafe(&mut *(callback as *mut F)))
+                .map_err(Error::from_panic)
+                .and_then(|v| v.into_block_return())
+            {
+                Ok(v) => v.as_rb_value(),
+                Err(e) => raise(e),
+            }
+        }
+
+        let (closure, keepalive) = wrap_closure(f);
+        let call_func = call::<F, R> as unsafe extern "C" fn(*mut c_void) -> VALUE;
+        let thread = Self(unsafe {
+            NonZeroValue::new_unchecked(protect(|| {
+                Value::new(rb_thread_create(Some(call_func), closure as *mut c_void))
+            })?)
+        });
+
+        thread.ivar_set("__rust_closure", keepalive)?;
+        Ok(thread)
+    }
+
+    ///
+    pub fn wait_fd_readable(fd: c_int) -> Result<(), Error> {
+        protect(|| {
+            unsafe { rb_thread_wait_fd(fd) };
+            Value::default()
+        })?;
+        Ok(())
+    }
+
+    ///
+    pub fn wait_fd_writable(fd: c_int) -> Result<(), Error> {
+        protect(|| {
+            unsafe { rb_thread_fd_writable(fd) };
+            Value::default()
+        })?;
+        Ok(())
+    }
+}
diff --git a/src/try_convert.rs b/src/try_convert.rs
index 88f93dd5..f72f6ad6 100644
--- a/src/try_convert.rs
+++ b/src/try_convert.rs
@@ -5,11 +5,12 @@ use crate::ruby_sys::{rb_get_path, rb_num2dbl};
 use crate::{
     debug_assert_value,
     error::{protect, Error},
-    exception,
+    exception::{self, type_error},
     integer::Integer,
     r_array::RArray,
     r_hash::RHash,
     r_string::RString,
+    symbol::Symbol,
     value::{Fixnum, Flonum, Value, QNIL},
 };
 
@@ -34,6 +35,27 @@ pub trait TryConvertForArg: Sized {
     fn try_convert_for_arg(val: Value) -> Result<Self, Error>;
 }
 
+impl TryConvert for () {
+    fn try_convert(val: Value) -> Result<Self, Error> {
+        if val.is_nil() {
+            Ok(())
+        } else {
+            Err(Error::new(
+                type_error(),
+                format!("no implicit conversion of {} into Unit", unsafe {
+                    val.classname()
+                }),
+            ))
+        }
+    }
+}
+
+impl TryConvertOwned for () {
+    fn try_convert_owned(val: Value) -> Result<Self, Error> {
+        Self::try_convert(val)
+    }
+}
+
 impl<T> TryConvert for Option<T>
 where
     T: TryConvert,
@@ -1040,6 +1062,494 @@ impl<const N: usize> ArgList for [Value; N] {
     }
 }
 
+/// Trait for types that can be used as keyword arguments when calling Ruby
+/// methods.
+pub trait KwArgList {
+    /// The type of the arguments list. Must convert to `&[Value]` with
+    /// [`AsRef`].
+    type Output: AsRef<[(Symbol, Value)]>;
+
+    /// Convert `self` into a type that can be used as Ruby keyword arguments.
+    fn into_kwarg_list(self) -> Self::Output;
+}
+
+impl<'a> KwArgList for &'a [(Symbol, Value)] {
+    type Output = &'a [(Symbol, Value)];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        self
+    }
+}
+
+impl KwArgList for () {
+    type Output = [(Symbol, Value); 0];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        []
+    }
+}
+
+impl<A1, A2> KwArgList for ((A1, A2),)
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 1];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [(self.0 .0.into(), self.0 .1.into())]
+    }
+}
+
+impl<A1, A2, B1, B2> KwArgList for ((A1, A2), (B1, B2))
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 2];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2> KwArgList for ((A1, A2), (B1, B2), (C1, C2))
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 3];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2> KwArgList for ((A1, A2), (B1, B2), (C1, C2), (D1, D2))
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 4];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2> KwArgList
+    for ((A1, A2), (B1, B2), (C1, C2), (D1, D2), (E1, E2))
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 5];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2> KwArgList
+    for ((A1, A2), (B1, B2), (C1, C2), (D1, D2), (E1, E2), (F1, F2))
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 6];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2> KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 7];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2> KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+        (H1, H2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+    H1: Into<Symbol>,
+    H2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 8];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+            (self.7 .0.into(), self.7 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2> KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+        (H1, H2),
+        (I1, I2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+    H1: Into<Symbol>,
+    H2: Into<Value>,
+    I1: Into<Symbol>,
+    I2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 9];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+            (self.7 .0.into(), self.7 .1.into()),
+            (self.8 .0.into(), self.8 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2, J1, J2> KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+        (H1, H2),
+        (I1, I2),
+        (J1, J2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+    H1: Into<Symbol>,
+    H2: Into<Value>,
+    I1: Into<Symbol>,
+    I2: Into<Value>,
+    J1: Into<Symbol>,
+    J2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 10];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+            (self.7 .0.into(), self.7 .1.into()),
+            (self.8 .0.into(), self.8 .1.into()),
+            (self.9 .0.into(), self.9 .1.into()),
+        ]
+    }
+}
+
+impl<A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2, G1, G2, H1, H2, I1, I2, J1, J2, K1, K2>
+    KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+        (H1, H2),
+        (I1, I2),
+        (J1, J2),
+        (K1, K2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+    H1: Into<Symbol>,
+    H2: Into<Value>,
+    I1: Into<Symbol>,
+    I2: Into<Value>,
+    J1: Into<Symbol>,
+    J2: Into<Value>,
+    K1: Into<Symbol>,
+    K2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 11];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+            (self.7 .0.into(), self.7 .1.into()),
+            (self.8 .0.into(), self.8 .1.into()),
+            (self.9 .0.into(), self.9 .1.into()),
+            (self.10 .0.into(), self.10 .1.into()),
+        ]
+    }
+}
+
+impl<
+        A1,
+        A2,
+        B1,
+        B2,
+        C1,
+        C2,
+        D1,
+        D2,
+        E1,
+        E2,
+        F1,
+        F2,
+        G1,
+        G2,
+        H1,
+        H2,
+        I1,
+        I2,
+        J1,
+        J2,
+        K1,
+        K2,
+        L1,
+        L2,
+    > KwArgList
+    for (
+        (A1, A2),
+        (B1, B2),
+        (C1, C2),
+        (D1, D2),
+        (E1, E2),
+        (F1, F2),
+        (G1, G2),
+        (H1, H2),
+        (I1, I2),
+        (J1, J2),
+        (K1, K2),
+        (L1, L2),
+    )
+where
+    A1: Into<Symbol>,
+    A2: Into<Value>,
+    B1: Into<Symbol>,
+    B2: Into<Value>,
+    C1: Into<Symbol>,
+    C2: Into<Value>,
+    D1: Into<Symbol>,
+    D2: Into<Value>,
+    E1: Into<Symbol>,
+    E2: Into<Value>,
+    F1: Into<Symbol>,
+    F2: Into<Value>,
+    G1: Into<Symbol>,
+    G2: Into<Value>,
+    H1: Into<Symbol>,
+    H2: Into<Value>,
+    I1: Into<Symbol>,
+    I2: Into<Value>,
+    J1: Into<Symbol>,
+    J2: Into<Value>,
+    K1: Into<Symbol>,
+    K2: Into<Value>,
+    L1: Into<Symbol>,
+    L2: Into<Value>,
+{
+    type Output = [(Symbol, Value); 12];
+
+    fn into_kwarg_list(self) -> Self::Output {
+        [
+            (self.0 .0.into(), self.0 .1.into()),
+            (self.1 .0.into(), self.1 .1.into()),
+            (self.2 .0.into(), self.2 .1.into()),
+            (self.3 .0.into(), self.3 .1.into()),
+            (self.4 .0.into(), self.4 .1.into()),
+            (self.5 .0.into(), self.5 .1.into()),
+            (self.6 .0.into(), self.6 .1.into()),
+            (self.7 .0.into(), self.7 .1.into()),
+            (self.8 .0.into(), self.8 .1.into()),
+            (self.9 .0.into(), self.9 .1.into()),
+            (self.10 .0.into(), self.10 .1.into()),
+            (self.11 .0.into(), self.11 .1.into()),
+        ]
+    }
+}
+
 pub trait RArrayArgList {
     fn into_array_arg_list(self) -> RArray;
 }
diff --git a/src/value.rs b/src/value.rs
index 09b4abc3..c1665003 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -22,6 +22,9 @@ use crate::ruby_sys::{
     rb_sym2id, rb_ull2inum, ruby_fl_type, ruby_special_consts, ruby_value_type, RBasic, ID, VALUE,
 };
 
+#[cfg(ruby_gte_2_7)]
+use crate::ruby_sys::rb_funcall_with_block_kw;
+
 // These don't seem to appear consistently in bindgen output, not sure if they
 // aren't consistently defined in the headers or what. Lets just do it
 // ourselves.
@@ -41,9 +44,10 @@ use crate::{
     module::Module,
     r_bignum::RBignum,
     r_float::RFloat,
+    r_hash::RHash,
     r_string::RString,
     symbol::Symbol,
-    try_convert::{ArgList, TryConvert, TryConvertOwned},
+    try_convert::{ArgList, KwArgList, TryConvert, TryConvertOwned},
 };
 
 /// Debug assertation that the Value hasn't been garbage collected.
@@ -586,6 +590,50 @@ impl Value {
         }
     }
 
+    #[cfg(ruby_gte_2_7)]
+    /// Call the method named `method` on `self` with `args`, `kwargs` and `block`.
+    ///
+    /// Similar to [`funcall_with_block`](Value::funcall_with_block), but passes `kwargs` as a Ruby
+    pub fn funcall_with_block_kw<M, A, K, T>(
+        self,
+        method: M,
+        args: A,
+        kwargs: K,
+        block: Proc,
+    ) -> Result<T, Error>
+    where
+        M: Into<Id>,
+        A: ArgList,
+        K: KwArgList,
+        T: TryConvert,
+    {
+        unsafe {
+            let id = method.into();
+            let args = args.into_arg_list();
+            let args = args.as_ref();
+            let argc = args.len() as c_int;
+            let mut slice = args.to_vec();
+            let kwargs = kwargs
+                .into_kwarg_list()
+                .as_ref()
+                .into_iter()
+                .copied()
+                .collect::<RHash>();
+            slice.push(kwargs.into());
+            protect(move || {
+                Value::new(rb_funcall_with_block_kw(
+                    self.as_rb_value(),
+                    id.as_rb_id(),
+                    argc,
+                    slice.as_ptr() as *const VALUE,
+                    block.as_rb_value(),
+                    true.into(),
+                ))
+            })
+            .and_then(|v| v.try_convert())
+        }
+    }
+
     /// Call the method named `method` on `self` with `args` and `block`.
     ///
     /// Similar to [`funcall`](Value::funcall), but passes `block` as a Ruby
@@ -1163,6 +1211,17 @@ impl<T> DerefMut for BoxValue<T> {
     }
 }
 
+impl<T> Clone for BoxValue<T>
+where
+    T: Clone,
+{
+    fn clone(&self) -> Self {
+        let mut boxed = self.0.clone();
+        unsafe { rb_gc_register_address(boxed.as_mut() as *mut _ as *mut VALUE) };
+        Self(boxed)
+    }
+}
+
 impl<T> fmt::Display for BoxValue<T>
 where
     T: ReprValue,
diff --git a/tests/gc.rs b/tests/gc.rs
new file mode 100644
index 00000000..e63b3bc5
--- /dev/null
+++ b/tests/gc.rs
@@ -0,0 +1,20 @@
+use magnus::{embed::init, eval, gc, value::BoxValue, RString, Value};
+
+#[inline(never)]
+fn box_value() -> BoxValue<RString> {
+    BoxValue::new(RString::new("foo"))
+}
+
+#[test]
+fn it_boxvalue_clone() {
+    let _cleanup = unsafe { init() };
+    let boxed = box_value();
+    let cloned = boxed.clone();
+    drop(boxed);
+
+    eval::<Value>(r#"1024.times.map {|i| "test#{i}"}"#).unwrap();
+    gc::start();
+    let result: String = eval!(r#"foo + "bar""#, foo = cloned).unwrap();
+
+    assert_eq!(result, "foobar");
+}