use std::{fmt, ptr::NonNull}; use rb_sys::{self, rb_check_typeddata, rb_data_typed_object_wrap, ruby_value_type, VALUE}; use crate::{ class::RClass, error::{protect, Error}, into_value::IntoValue, module::Module, object::Object, typed_data::TypedData, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RTypedData` /// /// Functions to wrap Rust data in a Ruby object. /// /// See also [`typed_data::Obj`](Ruby#typed_dataobj) and the [`RTypedData`] /// type. impl Ruby { /// Wrap the Rust type `T` in a Ruby object. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// /// let value = ruby.wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[inline] pub fn wrap(&self, data: T) -> RTypedData where T: TypedData, { let class = T::class_for(self, &data); self.wrap_as(data, class) } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// let point_sub_class = ruby.define_class("SubPoint", point_class)?; /// /// let value = ruby.wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call /// the `initialize` method, subclasses would need to override the class /// `new` method rather than `initialize`) /// /// ``` /// use magnus::{function, method, prelude::*, Error, RClass, RTypedData, Ruby, Value}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(ruby: &Ruby, class: RClass, x: isize, y: isize) -> RTypedData { /// ruby.wrap_as(Self { x, y }, class) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", method!(Point::new, 2))?; /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1))?; /// /// let value: Value = ruby.eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// )?; /// /// assert!(value.is_kind_of(ruby.class_object().const_get::<_, RClass>("SubPoint")?)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` pub fn wrap_as(&self, data: T, class: RClass) -> RTypedData where T: TypedData, { debug_assert!( class.is_inherited(T::class(self)), "{} is not a subclass of {}", class, T::class(self) ); let boxed = Box::new(data); unsafe { let value_ptr = rb_data_typed_object_wrap( class.as_rb_value(), Box::into_raw(boxed) as *mut _, T::data_type().as_rb_data_type() as *const _, ); RTypedData(NonZeroValue::new_unchecked(Value::new(value_ptr))) } } } /// A Value pointer to a RTypedData struct, Ruby’s internal representation of /// objects that wrap foreign types. /// /// See also [`typed_data::Obj`](crate::typed_data::Obj). /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#rtypeddata) for methods to create /// an `RTypedData`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RTypedData(NonZeroValue); impl RTypedData { /// Return `Some(RTypedData)` if `val` is a `RTypedData`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, Error, RTypedData, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", function!(|x, y| Point { x, y }, 2))?; /// /// assert!(RTypedData::from_value(ruby.eval(r#"Point.new(1, 2)"#)?).is_some()); /// assert!(RTypedData::from_value(ruby.eval(r#"Object.new"#)?).is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_DATA) .then(|| NonNull::new_unchecked(val.as_rb_value() as *mut rb_sys::RTypedData)) .and_then(|typed_data| { let typed_flag = typed_data.as_ref().typed_flag; (typed_flag != 0 && typed_flag <= 3) .then(|| Self(NonZeroValue::new_unchecked(val))) }) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Wrap the Rust type `T` in a Ruby object. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::wrap`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// /// let value = RTypedData::wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::wrap` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap(data: T) -> Self where T: TypedData, { get_ruby!().wrap(data) } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`, or /// if called from a non-Ruby thread. See [`Ruby::wrap_as`] for a version /// that can not be called from a non-Ruby thread, so will not panic for /// that reason. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// let point_sub_class = define_class("SubPoint", point_class).unwrap(); /// /// let value = RTypedData::wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call /// the `initialize` method, subclasses would need to override the class /// `new` method rather than `initialize`) /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// class, define_class, eval, function, method, prelude::*, RClass, RTypedData, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(class: RClass, x: isize, y: isize) -> RTypedData { /// RTypedData::wrap_as(Self { x, y }, class) /// } /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// point_class /// .define_singleton_method("new", method!(Point::new, 2)) /// .unwrap(); /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1)) /// .unwrap(); /// /// let value: Value = eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// ) /// .unwrap(); /// /// assert!(value.is_kind_of(class::object().const_get::<_, RClass>("SubPoint").unwrap())); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::wrap_as` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap_as(data: T, class: RClass) -> Self where T: TypedData, { get_ruby!().wrap_as(data, class) } /// Get a reference to the Rust type wrapped in the Ruby object `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// #[derive(Debug, PartialEq, Eq)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_class("Point", ruby.class_object())?; /// let value = ruby.wrap(Point { x: 4, y: 2 }); /// /// assert_eq!(value.get::()?, &Point { x: 4, y: 2 }); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn get(&self) -> Result<&T, Error> where T: TypedData, { unsafe { self.get_unconstrained() } } /// Get a reference to the Rust type wrapped in the Ruby object `self`. /// /// # Safety /// /// This method can magic any lifetime needed out of thin air, even /// `'static`. #[inline] pub(crate) unsafe fn get_unconstrained<'a, T>(self) -> Result<&'a T, Error> where T: TypedData, { debug_assert_value!(self); let handle = Ruby::get_with(self); let mut res = None; let _ = protect(|| { res = (rb_check_typeddata( self.as_rb_value(), T::data_type().as_rb_data_type() as *const _, ) as *const T) .as_ref(); handle.qnil() }); res.ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "no implicit conversion of {} into {}", self.classname(), T::class(&handle) ), ) }) } } impl fmt::Display for RTypedData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RTypedData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RTypedData { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RTypedData {} unsafe impl private::ReprValue for RTypedData {} impl ReprValue for RTypedData {}