magnus/typed_data.rs
1//! Types and Traits for wrapping Rust types as Ruby objects.
2//!
3//! This, along with [`RTypedData`], provides a Rust API to the
4//! `rb_data_typed_object_wrap` function from Ruby's C API.
5
6use std::{
7 collections::hash_map::DefaultHasher,
8 ffi::{CStr, c_void},
9 fmt,
10 hash::Hasher,
11 marker::PhantomData,
12 mem::size_of_val,
13 ops::Deref,
14 panic::catch_unwind,
15 ptr,
16};
17
18use rb_sys::{
19 self, RTYPEDDATA_GET_DATA, VALUE, rb_data_type_struct__bindgen_ty_1, rb_data_type_t,
20 rb_gc_writebarrier, rb_gc_writebarrier_unprotect, rb_obj_reveal, rb_singleton_class_attached,
21 rb_singleton_class_clone,
22 rbimpl_typeddata_flags::{self, RUBY_TYPED_FREE_IMMEDIATELY, RUBY_TYPED_WB_PROTECTED},
23 size_t,
24};
25
26use crate::{
27 Ruby,
28 class::RClass,
29 error::{Error, bug_from_panic},
30 gc::{self, Mark},
31 into_value::IntoValue,
32 object::Object,
33 r_typed_data::RTypedData,
34 scan_args::{get_kwargs, scan_args},
35 try_convert::TryConvert,
36 value::{
37 ReprValue, Value,
38 private::{self, ReprValue as _},
39 },
40};
41
42/// A C struct containing metadata on a Rust type, for use with the
43/// `rb_data_typed_object_wrap` API.
44#[repr(transparent)]
45pub struct DataType(rb_data_type_t);
46
47impl DataType {
48 /// Create a new `DataTypeBuilder`.
49 ///
50 /// `name` should be unique per wrapped type. It does not need to be a
51 /// valid Ruby identifier.
52 ///
53 /// See [`data_type_builder`](macro@crate::data_type_builder) to create a
54 /// `DataTypeBuilder` with a `'static CStr` `name` from a string literal.
55 /// # Examples
56 ///
57 /// ```
58 /// use magnus::DataType;
59 /// # use magnus::DataTypeFunctions;
60 /// # #[derive(DataTypeFunctions)]
61 /// # struct Example();
62 ///
63 /// DataType::builder::<Example>(c"example");
64 /// ```
65 pub const fn builder<T>(name: &'static CStr) -> DataTypeBuilder<T>
66 where
67 T: DataTypeFunctions,
68 {
69 DataTypeBuilder::new(name)
70 }
71
72 #[inline]
73 pub(crate) fn as_rb_data_type(&self) -> &rb_data_type_t {
74 &self.0
75 }
76}
77
78unsafe impl Send for DataType {}
79unsafe impl Sync for DataType {}
80
81/// A helper trait used to define functions associated with a [`DataType`].
82pub trait DataTypeFunctions
83where
84 Self: Send + Sized,
85{
86 /// Called when the Ruby wrapper object is garbage collected.
87 ///
88 /// This can be implemented to perform Ruby-specific clean up when your
89 /// type is no longer referenced from Ruby, but it is likely easier to do
90 /// this in a [`Drop`] implementation for your type.
91 ///
92 /// This function will always be called by Ruby on GC, it can not be opted
93 /// out of.
94 ///
95 /// The default implementation simply drops `self`.
96 ///
97 /// If this function (or the [`Drop`] implementation for your type) call
98 /// Ruby APIs you should not enable the `free_immediately` flag with the
99 /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData)
100 /// macro or [`DataTypeBuilder::free_immediately`].
101 ///
102 /// This function **must not** panic. The process will abort if this
103 /// function panics.
104 fn free(self: Box<Self>) {}
105
106 /// Called when Ruby marks this object as part of garbage collection.
107 ///
108 /// If your type contains any Ruby values you must mark each of those
109 /// values in this function to avoid them being garbage collected.
110 ///
111 /// This function is only called when the `mark` flag is set with the
112 /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData)
113 /// macro or [`DataTypeBuilder::mark`].
114 ///
115 /// The default implementation does nothing.
116 ///
117 /// This function **must not** panic. The process will abort if this
118 /// function panics.
119 fn mark(&self, #[allow(unused_variables)] marker: &gc::Marker) {}
120
121 /// Called by Ruby to establish the memory size of this data, to optimise
122 /// when garbage collection happens.
123 ///
124 /// This function is only called when the `size` flag is set with the
125 /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData)
126 /// macro or [`DataTypeBuilder::mark`].
127 ///
128 /// The default implementation delegates to [`std::mem::size_of_val`].
129 ///
130 /// This function **must not** panic. The process will abort if this
131 /// function panics.
132 fn size(&self) -> usize {
133 size_of_val(self)
134 }
135
136 /// Called during garbage collection.
137 ///
138 /// If your type contains any Ruby values that you have marked as moveable
139 /// in your [`mark`](Self::mark) function, you must update them in this
140 /// function using [`gc::Compactor::location`].
141 ///
142 /// Ruby considers values are moveable if marked with the
143 /// [`gc::Marker::mark_movable`] function. Other marking functions such as
144 /// [`gc::Marker::mark`] will prevent values being moved.
145 ///
146 /// As it is only safe for this function to receive a shared `&self`
147 /// reference, you must implement interior mutability to be able to update
148 /// values. This is very hard to do correctly, and it is recommended to
149 /// simply avoid using [`gc::Marker::mark_movable`] and `compact`.
150 ///
151 /// This function is only called when the `compact` flag is set with the
152 /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData)
153 /// macro or [`DataTypeBuilder::mark`].
154 ///
155 /// The default implementation does nothing.
156 ///
157 /// This function **must not** panic. The process will abort if this
158 /// function panics.
159 fn compact(&self, #[allow(unused_variables)] compactor: &gc::Compactor) {}
160
161 /// Extern wrapper for `free`. Don't define or call.
162 ///
163 /// # Safety
164 ///
165 /// `ptr` must be a valid pointer to a `Box<Self>`, and must not be aliased
166 /// This function will free the memory pointed to by `ptr`.
167 ///
168 /// This function must not panic.
169 #[doc(hidden)]
170 unsafe extern "C" fn extern_free(ptr: *mut c_void) {
171 unsafe {
172 if let Err(e) = catch_unwind(|| Self::free(Box::from_raw(ptr as *mut _))) {
173 bug_from_panic(e, "panic in DataTypeFunctions::free")
174 }
175 }
176 }
177
178 /// Extern wrapper for `mark`. Don't define or call.
179 ///
180 /// # Safety
181 ///
182 /// `ptr` must be a valid pointer to a `Self`, and must not be aliased.
183 ///
184 /// This function must not panic.
185 #[doc(hidden)]
186 unsafe extern "C" fn extern_mark(ptr: *mut c_void) {
187 unsafe {
188 let marker = gc::Marker::new(Ruby::get_unchecked());
189 if let Err(e) = catch_unwind(|| Self::mark(&*(ptr as *mut Self), &marker)) {
190 bug_from_panic(e, "panic in DataTypeFunctions::mark")
191 }
192 }
193 }
194
195 /// Extern wrapper for `size`. Don't define or call.
196 ///
197 /// # Safety
198 ///
199 /// `ptr` must be a valid pointer to a `Self`.
200 ///
201 /// This function must not panic.
202 #[doc(hidden)]
203 unsafe extern "C" fn extern_size(ptr: *const c_void) -> size_t {
204 unsafe {
205 match catch_unwind(|| Self::size(&*(ptr as *const Self)) as size_t) {
206 Ok(v) => v,
207 Err(e) => bug_from_panic(e, "panic in DataTypeFunctions::size"),
208 }
209 }
210 }
211
212 /// Extern wrapper for `compact`. Don't define or call.
213 ///
214 /// # Safety
215 ///
216 /// `ptr` must be a valid pointer to a `Self`, and must not be aliased.
217 ///
218 /// This function must not panic.
219 #[doc(hidden)]
220 unsafe extern "C" fn extern_compact(ptr: *mut c_void) {
221 unsafe {
222 let compactor = gc::Compactor::new(Ruby::get_unchecked());
223 if let Err(e) = catch_unwind(|| Self::compact(&*(ptr as *mut Self), &compactor)) {
224 bug_from_panic(e, "panic in DataTypeFunctions::compact")
225 }
226 }
227 }
228}
229
230/// A builder for [`DataType`].
231pub struct DataTypeBuilder<T> {
232 name: &'static CStr,
233 mark: bool,
234 size: bool,
235 compact: bool,
236 free_immediately: bool,
237 wb_protected: bool,
238 frozen_shareable: bool,
239 phantom: PhantomData<T>,
240}
241
242/// Create a new [`DataTypeBuilder`].
243///
244/// `name` should be unique per wrapped type. It does not need to be a
245/// valid Ruby identifier.
246///
247/// `data_type_builder!(Example, "example")` is equivalent to
248/// `DataTypeBuilder::<Example>::new` with a `name` argument of `"example"` as
249/// a `'static CStr`.
250#[deprecated(note = "please use `DataTypeBuilder::<Example>::new(c\"example\")` instead")]
251#[macro_export]
252macro_rules! data_type_builder {
253 ($t:ty, $name:literal) => {
254 $crate::typed_data::DataTypeBuilder::<$t>::new(unsafe {
255 std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes())
256 })
257 };
258}
259
260impl<T> DataTypeBuilder<T>
261where
262 T: DataTypeFunctions,
263{
264 /// Create a new `DataTypeBuilder`.
265 ///
266 /// `name` should be unique per wrapped type. It does not need to be a
267 /// valid Ruby identifier.
268 ///
269 /// # Examples
270 ///
271 /// ```
272 /// use magnus::typed_data::DataTypeBuilder;
273 /// # use magnus::DataTypeFunctions;
274 /// # #[derive(DataTypeFunctions)]
275 /// # struct Example();
276 ///
277 /// DataTypeBuilder::<Example>::new(c"example");
278 /// ```
279 pub const fn new(name: &'static CStr) -> Self {
280 Self {
281 name,
282 mark: false,
283 size: false,
284 compact: false,
285 free_immediately: false,
286 wb_protected: false,
287 frozen_shareable: false,
288 phantom: PhantomData,
289 }
290 }
291
292 /// Enable using the the `mark` function from `<T as DataTypeFunctions>`.
293 pub const fn mark(mut self) -> Self {
294 self.mark = true;
295 self
296 }
297
298 /// Enable using the the `size` function from `<T as DataTypeFunctions>`.
299 pub const fn size(mut self) -> Self {
300 self.size = true;
301 self
302 }
303
304 /// Enable using the the `compact` function from `<T as DataTypeFunctions>`.
305 pub const fn compact(mut self) -> Self {
306 self.compact = true;
307 self
308 }
309
310 /// Enable the 'free_immediately' flag.
311 ///
312 /// This is safe to do as long as the `<T as DataTypeFunctions>::free`
313 /// function or `T`'s drop function don't call Ruby in any way.
314 ///
315 /// If safe this should be enabled as this performs better and is more
316 /// memory efficient.
317 pub const fn free_immediately(mut self) -> Self {
318 self.free_immediately = true;
319 self
320 }
321
322 /// Enable the 'write barrier protected' flag.
323 ///
324 /// Types that contain Ruby values by default do not participate in
325 /// generational GC (they are scanned every GC). This flag asserts all
326 /// operations that write Ruby values to this type are protected with
327 /// write barriers (see [`Writebarrier::writebarrier`]) so this
328 /// type can participate in generational GC.
329 ///
330 /// The write barrier is hard to get right. Magnus recommends you do not use
331 /// this flag.
332 pub const fn wb_protected(mut self) -> Self {
333 self.wb_protected = true;
334 self
335 }
336
337 /// Consume the builder and create a DataType.
338 pub const fn build(self) -> DataType {
339 let mut flags = 0_usize as VALUE;
340 if self.free_immediately {
341 flags |= RUBY_TYPED_FREE_IMMEDIATELY as VALUE;
342 }
343 if self.wb_protected || !self.mark {
344 flags |= RUBY_TYPED_WB_PROTECTED as VALUE;
345 }
346 if self.frozen_shareable {
347 flags |= rbimpl_typeddata_flags::RUBY_TYPED_FROZEN_SHAREABLE as VALUE;
348 }
349 let dmark = if self.mark {
350 Some(T::extern_mark as _)
351 } else {
352 None
353 };
354 let dfree = Some(T::extern_free as _);
355 let dsize = if self.size {
356 Some(T::extern_size as _)
357 } else {
358 None
359 };
360 let dcompact = if self.compact {
361 Some(T::extern_compact as _)
362 } else {
363 None
364 };
365 DataType(rb_data_type_t {
366 wrap_struct_name: self.name.as_ptr() as _,
367 function: rb_data_type_struct__bindgen_ty_1 {
368 dmark,
369 dfree,
370 dsize,
371 dcompact,
372 #[cfg(ruby_lt_4_1)]
373 reserved: [ptr::null_mut(); 1],
374 #[cfg(ruby_gte_4_1)]
375 // Ruby 4.1 expands rtypeddata's reserved slots; keep in sync with
376 // https://github.com/ruby/ruby/blob/master/include/ruby/internal/core/rtypeddata.h
377 reserved: [ptr::null_mut(); 7],
378 #[cfg(ruby_gte_4_1)]
379 handle_weak_references: None,
380 },
381 parent: ptr::null(),
382 data: ptr::null_mut(),
383 flags,
384 })
385 }
386}
387
388impl<T> DataTypeBuilder<T>
389where
390 T: DataTypeFunctions + Sync,
391{
392 /// Enable the 'frozen_shareable' flag.
393 ///
394 /// Set this if your type is thread safe when the Ruby wrapper object is
395 /// frozen.
396 pub const fn frozen_shareable(mut self) -> Self {
397 self.frozen_shareable = true;
398 self
399 }
400}
401
402/// A trait for Rust types that can be used with the
403/// `rb_data_typed_object_wrap` API.
404///
405/// # Safety
406///
407/// This trait is unsafe to implement as the fields of [`DataType`] returned by
408/// [`TypedData::data_type`] control low level behaviour that can go very wrong
409/// if set incorrectly. Implementing this trait is the only way a [`DataType`]
410/// can be passed to Ruby and result in safety violations, [`DataType`] is
411/// otherwise safe (but useless) to create.
412///
413/// The [`TypedData`](`derive@crate::TypedData`) or [`wrap`](`crate::wrap`)
414/// macros can help implementing this trait more safely.
415pub unsafe trait TypedData
416where
417 Self: Send + Sized,
418{
419 /// Should return the class for the Ruby object wrapping the Rust type.
420 ///
421 /// This can be overridden on a case by case basis by implementing
422 /// [`TypedData::class_for`], but the result of this function will always
423 /// be used in error messages if a value fails to convert to `Self`.
424 ///
425 /// If using [`class_for`](Self::class_for) it is advised to have this
426 /// function return the superclass of those returned by `class_for`.
427 ///
428 /// # Examples
429 ///
430 /// ```
431 /// use magnus::{RClass, Ruby, TypedData, prelude::*, value::Lazy};
432 /// # use magnus::DataType;
433 ///
434 /// struct Example();
435 ///
436 /// unsafe impl TypedData for Example {
437 /// fn class(ruby: &Ruby) -> RClass {
438 /// static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
439 /// let class = ruby.define_class("Example", ruby.class_object()).unwrap();
440 /// class.undef_default_alloc_func();
441 /// class
442 /// });
443 /// ruby.get_inner(&CLASS)
444 /// }
445 ///
446 /// // ...
447 /// # fn data_type() -> &'static DataType { unimplemented!() }
448 /// }
449 /// # Example();
450 /// ```
451 fn class(ruby: &Ruby) -> RClass;
452
453 /// Should return a static reference to a [`DataType`] with metadata about
454 /// the wrapped type.
455 ///
456 /// # Examples
457 ///
458 /// ```
459 /// use magnus::{DataType, DataTypeFunctions, TypedData, typed_data::DataTypeBuilder};
460 /// # use magnus::{RClass, Ruby};
461 ///
462 /// #[derive(DataTypeFunctions)]
463 /// struct Example();
464 ///
465 /// unsafe impl TypedData for Example {
466 /// # fn class(_: &Ruby) -> RClass { unimplemented!() }
467 /// // ...
468 ///
469 /// fn data_type() -> &'static DataType {
470 /// static DATA_TYPE: DataType = DataTypeBuilder::<Example>::new(c"example").build();
471 /// &DATA_TYPE
472 /// }
473 /// }
474 /// # Example();
475 /// ```
476 fn data_type() -> &'static DataType;
477
478 /// Used to customise the class wrapping a specific value of `Self`.
479 ///
480 /// The provided implementation simply returns the value of
481 /// [`TypedData::class`].
482 ///
483 /// The classes returned by this function must be subclasses of
484 /// `TypedData::class`. `TypedData::class` will always be used in error
485 /// messages if a value fails to convert to `Self`.
486 ///
487 /// See also [`Obj::wrap_as`]/[`RTypedData::wrap_as`].
488 ///
489 /// # Examples
490 ///
491 /// ```
492 /// use magnus::{RClass, Ruby, TypedData, prelude::*, value::Lazy};
493 /// # use magnus::DataType;
494 ///
495 /// enum Example {
496 /// A,
497 /// B,
498 /// }
499 ///
500 /// unsafe impl TypedData for Example {
501 /// # fn class(_: &Ruby) -> RClass { unimplemented!() }
502 /// # fn data_type() -> &'static DataType { unimplemented!() }
503 /// // ...
504 ///
505 /// fn class_for(ruby: &Ruby, value: &Self) -> RClass {
506 /// static A: Lazy<RClass> = Lazy::new(|ruby| {
507 /// let class = ruby.define_class("A", Example::class(ruby)).unwrap();
508 /// class.undef_default_alloc_func();
509 /// class
510 /// });
511 /// static B: Lazy<RClass> = Lazy::new(|ruby| {
512 /// let class = ruby.define_class("B", Example::class(ruby)).unwrap();
513 /// class.undef_default_alloc_func();
514 /// class
515 /// });
516 /// match value {
517 /// Self::A => ruby.get_inner(&A),
518 /// Self::B => ruby.get_inner(&B),
519 /// }
520 /// }
521 /// }
522 /// # let _ = (Example::A, Example::B);
523 /// ```
524 #[allow(unused_variables)]
525 fn class_for(ruby: &Ruby, value: &Self) -> RClass {
526 Self::class(ruby)
527 }
528}
529
530impl<T> TryConvert for &T
531where
532 T: TypedData,
533{
534 fn try_convert(val: Value) -> Result<Self, Error> {
535 let handle = Ruby::get_with(val);
536 unsafe {
537 RTypedData::from_value(val)
538 .ok_or_else(|| {
539 Error::new(
540 handle.exception_type_error(),
541 format!(
542 "no implicit conversion of {} into {}",
543 val.classname(),
544 T::class(&handle)
545 ),
546 )
547 })?
548 .get_unconstrained()
549 }
550 }
551}
552
553// This impl causes rustc to recommend `TypedData` in places it would be much
554// more helpful to instead `IntoValue`, so tell it not to.
555#[diagnostic::do_not_recommend]
556impl<T> IntoValue for T
557where
558 T: TypedData,
559{
560 #[inline]
561 fn into_value_with(self, handle: &Ruby) -> Value {
562 handle.wrap(self).into_value_with(handle)
563 }
564}
565
566/// A Ruby Object wrapping a Rust type `T`.
567///
568/// This is a Value pointer to a RTypedData struct, Ruby’s internal
569/// representation of objects that wrap foreign types. Unlike [`RTypedData`] it
570/// tracks the Rust type it should contains and errors early in [`TryConvert`]
571/// if types don't match.
572///
573/// See the [`ReprValue`] and [`Object`] traits for additional methods
574/// available on this type. See [`Ruby`](Ruby#typed_dataobj) for methods to
575/// create a `typed_data::Obj`.
576#[repr(transparent)]
577pub struct Obj<T> {
578 inner: RTypedData,
579 phantom: PhantomData<T>,
580}
581
582impl<T> Copy for Obj<T> where T: TypedData {}
583
584impl<T> Clone for Obj<T>
585where
586 T: TypedData,
587{
588 fn clone(&self) -> Self {
589 *self
590 }
591}
592
593/// # `typed_data::Obj`
594///
595/// Functions to wrap Rust data in a Ruby object.
596///
597/// See also [`RTypedData`](Ruby#rtypeddata) and the [`typed_data::Obj`](Obj)
598/// type.
599impl Ruby {
600 /// Wrap the Rust type `T` in a Ruby object.
601 ///
602 /// # Examples
603 ///
604 /// ```
605 /// use magnus::{Error, Ruby, prelude::*};
606 ///
607 /// #[magnus::wrap(class = "Point")]
608 /// struct Point {
609 /// x: isize,
610 /// y: isize,
611 /// }
612 ///
613 /// fn example(ruby: &Ruby) -> Result<(), Error> {
614 /// let point_class = ruby.define_class("Point", ruby.class_object())?;
615 ///
616 /// let value = ruby.obj_wrap(Point { x: 4, y: 2 });
617 /// assert!(value.is_kind_of(point_class));
618 ///
619 /// Ok(())
620 /// }
621 /// # Ruby::init(example).unwrap();
622 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
623 /// ```
624 pub fn obj_wrap<T>(&self, data: T) -> Obj<T>
625 where
626 T: TypedData,
627 {
628 Obj {
629 inner: self.wrap(data),
630 phantom: PhantomData,
631 }
632 }
633
634 /// Wrap the Rust type `T` in a Ruby object that is an instance of the
635 /// given `class`.
636 ///
637 /// See also [`TypedData::class_for`].
638 ///
639 /// # Panics
640 ///
641 /// Panics if `class` is not a subclass of `<T as TypedData>::class()`.
642 ///
643 /// # Examples
644 ///
645 /// ```
646 /// use magnus::{Error, Ruby, prelude::*};
647 ///
648 /// #[magnus::wrap(class = "Point")]
649 /// struct Point {
650 /// x: isize,
651 /// y: isize,
652 /// }
653 ///
654 /// fn example(ruby: &Ruby) -> Result<(), Error> {
655 /// let point_class = ruby.define_class("Point", ruby.class_object())?;
656 /// let point_sub_class = ruby.define_class("SubPoint", point_class)?;
657 ///
658 /// let value = ruby.obj_wrap_as(Point { x: 4, y: 2 }, point_sub_class);
659 /// assert!(value.is_kind_of(point_sub_class));
660 /// assert!(value.is_kind_of(point_class));
661 ///
662 /// Ok(())
663 /// }
664 /// # Ruby::init(example).unwrap();
665 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
666 /// ```
667 ///
668 /// Allowing a wrapped type to be subclassed from Ruby:
669 ///
670 /// (note, in this example `Point` does not have and does not call the
671 /// `initialize` method, subclasses would need to override the class `new`
672 /// method rather than `initialize`)
673 ///
674 /// ```
675 /// use magnus::{Error, RClass, Ruby, Value, function, method, prelude::*, typed_data};
676 ///
677 /// #[magnus::wrap(class = "Point")]
678 /// struct Point {
679 /// x: isize,
680 /// y: isize,
681 /// }
682 ///
683 /// impl Point {
684 /// fn new(ruby: &Ruby, class: RClass, x: isize, y: isize) -> typed_data::Obj<Self> {
685 /// ruby.obj_wrap_as(Self { x, y }, class)
686 /// }
687 /// }
688 ///
689 /// fn example(ruby: &Ruby) -> Result<(), Error> {
690 /// let point_class = ruby.define_class("Point", ruby.class_object())?;
691 /// point_class.define_singleton_method("new", method!(Point::new, 2))?;
692 /// point_class
693 /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1))?;
694 ///
695 /// let value: Value = ruby.eval(
696 /// r#"
697 /// class SubPoint < Point
698 /// end
699 /// SubPoint.new(4, 2)
700 /// "#,
701 /// )?;
702 ///
703 /// assert!(value.is_kind_of(ruby.class_object().const_get::<_, RClass>("SubPoint")?));
704 /// assert!(value.is_kind_of(point_class));
705 ///
706 /// Ok(())
707 /// }
708 /// # Ruby::init(example).unwrap();
709 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
710 /// ```
711 pub fn obj_wrap_as<T>(&self, data: T, class: RClass) -> Obj<T>
712 where
713 T: TypedData,
714 {
715 Obj {
716 inner: self.wrap_as(data, class),
717 phantom: PhantomData,
718 }
719 }
720}
721
722impl<T> Obj<T>
723where
724 T: TypedData,
725{
726 /// Wrap the Rust type `T` in a Ruby object.
727 ///
728 /// # Panics
729 ///
730 /// Panics if called from a non-Ruby thread. See [`Ruby::obj_wrap`] for the
731 /// non-panicking version.
732 ///
733 /// # Examples
734 ///
735 /// ```
736 /// # #![allow(deprecated)]
737 /// use magnus::{class, define_class, prelude::*, typed_data};
738 /// # let _cleanup = unsafe { magnus::embed::init() };
739 ///
740 /// #[magnus::wrap(class = "Point")]
741 /// struct Point {
742 /// x: isize,
743 /// y: isize,
744 /// }
745 ///
746 /// let point_class = define_class("Point", class::object()).unwrap();
747 ///
748 /// let value = typed_data::Obj::wrap(Point { x: 4, y: 2 });
749 /// assert!(value.is_kind_of(point_class));
750 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
751 /// ```
752 #[deprecated(note = "please use `Ruby::obj_wrap` instead")]
753 #[cfg(feature = "old-api")]
754 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
755 #[inline]
756 pub fn wrap(data: T) -> Self {
757 get_ruby!().obj_wrap(data)
758 }
759
760 /// Wrap the Rust type `T` in a Ruby object that is an instance of the
761 /// given `class`.
762 ///
763 /// See also [`TypedData::class_for`].
764 ///
765 /// # Panics
766 ///
767 /// Panics if `class` is not a subclass of `<T as TypedData>::class()`, or
768 /// if called from a non-Ruby thread. See [`Ruby::obj_wrap_as`] for a
769 /// version that can not be called from a non-Ruby thread, so will not
770 /// panic for that reason.
771 ///
772 /// # Examples
773 ///
774 /// ```
775 /// # #![allow(deprecated)]
776 /// use magnus::{class, define_class, prelude::*, typed_data};
777 /// # let _cleanup = unsafe { magnus::embed::init() };
778 ///
779 /// #[magnus::wrap(class = "Point")]
780 /// struct Point {
781 /// x: isize,
782 /// y: isize,
783 /// }
784 ///
785 /// let point_class = define_class("Point", class::object()).unwrap();
786 /// let point_sub_class = define_class("SubPoint", point_class).unwrap();
787 ///
788 /// let value = typed_data::Obj::wrap_as(Point { x: 4, y: 2 }, point_sub_class);
789 /// assert!(value.is_kind_of(point_sub_class));
790 /// assert!(value.is_kind_of(point_class));
791 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
792 /// ```
793 ///
794 /// Allowing a wrapped type to be subclassed from Ruby:
795 ///
796 /// (note, in this example `Point` does not have and does not call
797 /// the `initialize` method, subclasses would need to override the class
798 /// `new` method rather than `initialize`)
799 ///
800 /// ```
801 /// # #![allow(deprecated)]
802 /// use magnus::{
803 /// RClass, Value, class, define_class, eval, function, method, prelude::*, typed_data,
804 /// };
805 /// # let _cleanup = unsafe { magnus::embed::init() };
806 ///
807 /// #[magnus::wrap(class = "Point")]
808 /// struct Point {
809 /// x: isize,
810 /// y: isize,
811 /// }
812 ///
813 /// impl Point {
814 /// fn new(class: RClass, x: isize, y: isize) -> typed_data::Obj<Self> {
815 /// typed_data::Obj::wrap_as(Self { x, y }, class)
816 /// }
817 /// }
818 /// let point_class = define_class("Point", class::object()).unwrap();
819 /// point_class
820 /// .define_singleton_method("new", method!(Point::new, 2))
821 /// .unwrap();
822 /// point_class
823 /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1))
824 /// .unwrap();
825 ///
826 /// let value: Value = eval(
827 /// r#"
828 /// class SubPoint < Point
829 /// end
830 /// SubPoint.new(4, 2)
831 /// "#,
832 /// )
833 /// .unwrap();
834 ///
835 /// assert!(value.is_kind_of(class::object().const_get::<_, RClass>("SubPoint").unwrap()));
836 /// assert!(value.is_kind_of(point_class));
837 /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y;
838 /// ```
839 #[deprecated(note = "please use `Ruby::obj_wrap_as` instead")]
840 #[cfg(feature = "old-api")]
841 #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
842 #[inline]
843 pub fn wrap_as(data: T, class: RClass) -> Self {
844 get_ruby!().obj_wrap_as(data, class)
845 }
846
847 /// Get the raw pointer to the Rust type wrapped in the Ruby object `obj`.
848 ///
849 /// While it is safe to acquire this pointer it is unsafe to use. You must
850 /// ensure the Ruby object is kept alive, and the pointer is not aliased or
851 /// written to concurrently.
852 pub fn as_ptr(obj: Self) -> *mut T {
853 obj.inner.as_ptr().unwrap()
854 }
855}
856
857impl<T> Deref for Obj<T>
858where
859 T: TypedData,
860{
861 type Target = T;
862
863 /// Dereference to the Rust type wrapped in the Ruby object `self`.
864 ///
865 /// # Examples
866 ///
867 /// ```
868 /// use magnus::{Error, Ruby};
869 ///
870 /// #[magnus::wrap(class = "Point")]
871 /// #[derive(Debug, PartialEq, Eq)]
872 /// struct Point {
873 /// x: isize,
874 /// y: isize,
875 /// }
876 ///
877 /// fn example(ruby: &Ruby) -> Result<(), Error> {
878 /// ruby.define_class("Point", ruby.class_object())?;
879 /// let value = ruby.obj_wrap(Point { x: 4, y: 2 });
880 ///
881 /// assert_eq!(&*value, &Point { x: 4, y: 2 });
882 ///
883 /// Ok(())
884 /// }
885 /// # Ruby::init(example).unwrap()
886 /// ```
887 fn deref(&self) -> &Self::Target {
888 // Since we've already validated the inner during `TryConvert` via `RTypedData::get`, we
889 // can skip the extra checks and libruby calls and just access the data directly.
890 unsafe {
891 let data_ptr = RTYPEDDATA_GET_DATA(self.inner.as_rb_value()) as *mut Self::Target;
892 &*data_ptr
893 }
894 }
895}
896
897impl<T> fmt::Display for Obj<T>
898where
899 T: TypedData,
900{
901 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
902 write!(f, "{}", unsafe { self.to_s_infallible() })
903 }
904}
905
906impl<T> fmt::Debug for Obj<T>
907where
908 T: TypedData,
909{
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 write!(f, "{}", self.inspect())
912 }
913}
914
915impl<T> IntoValue for Obj<T>
916where
917 T: TypedData,
918{
919 #[inline]
920 fn into_value_with(self, handle: &Ruby) -> Value {
921 self.inner.into_value_with(handle)
922 }
923}
924
925impl<T> From<Obj<T>> for RTypedData
926where
927 T: TypedData,
928{
929 fn from(val: Obj<T>) -> Self {
930 val.inner
931 }
932}
933
934impl<T> Object for Obj<T> where T: TypedData {}
935
936unsafe impl<T> private::ReprValue for Obj<T> where T: TypedData {}
937
938impl<T> ReprValue for Obj<T> where T: TypedData {}
939
940impl<T> TryConvert for Obj<T>
941where
942 T: TypedData,
943{
944 fn try_convert(val: Value) -> Result<Self, Error> {
945 let handle = Ruby::get_with(val);
946 let inner = RTypedData::from_value(val).ok_or_else(|| {
947 Error::new(
948 handle.exception_type_error(),
949 format!(
950 "no implicit conversion of {} into {}",
951 unsafe { val.classname() },
952 T::class(&handle)
953 ),
954 )
955 })?;
956
957 // check it really does contain a T
958 inner.get::<T>()?;
959
960 Ok(Self {
961 inner,
962 phantom: PhantomData,
963 })
964 }
965}
966
967/// A trait for types that can be used with the `rb_gc_writebarrier` API.
968pub trait Writebarrier: ReprValue {
969 /// Inform Ruby that `self` contains a reference to `new`.
970 ///
971 /// If you have a Rust type that contains Ruby values, and itself is
972 /// wrapped as a Ruby object, and you choose to enable the `wb_protected`
973 /// flag so that it can participate in generational GC then all operations
974 /// that add a Ruby value to your data type must call this function.
975 ///
976 /// The write barrier is hard to get right. Magnus recommends you do not
977 /// enable the `wb_protected` flag, and thus you don't need to use this function.
978 ///
979 /// # Examples
980 ///
981 /// ```
982 /// use std::cell::RefCell;
983 ///
984 /// use magnus::{
985 /// DataTypeFunctions, Error, Ruby, TypedData, Value, function, gc, method,
986 /// prelude::*,
987 /// typed_data::{Obj, Writebarrier},
988 /// value::Opaque,
989 /// };
990 ///
991 /// #[derive(TypedData)]
992 /// #[magnus(class = "MyVec", free_immediately, mark, wb_protected)]
993 /// struct MyVec {
994 /// values: RefCell<Vec<Opaque<Value>>>,
995 /// }
996 ///
997 /// impl DataTypeFunctions for MyVec {
998 /// fn mark(&self, marker: &gc::Marker) {
999 /// marker.mark_slice(self.values.borrow().as_slice());
1000 /// }
1001 /// }
1002 ///
1003 /// impl MyVec {
1004 /// fn new() -> Self {
1005 /// Self {
1006 /// values: RefCell::new(Vec::new()),
1007 /// }
1008 /// }
1009 ///
1010 /// fn push(rb_self: Obj<Self>, val: Value) -> Obj<Self> {
1011 /// rb_self.values.borrow_mut().push(val.into());
1012 /// rb_self.writebarrier(rb_self);
1013 /// rb_self
1014 /// }
1015 /// }
1016 ///
1017 /// fn example(ruby: &Ruby) -> Result<(), Error> {
1018 /// let class = ruby.define_class("MyVec", ruby.class_object())?;
1019 /// class.define_singleton_method("new", function!(MyVec::new, 0))?;
1020 /// class.define_method("push", method!(MyVec::push, 1))?;
1021 ///
1022 /// let _: Value = ruby.eval(
1023 /// r#"
1024 /// vec = MyVec.new
1025 /// vec.push("test")
1026 /// vec.push("example")
1027 /// "#,
1028 /// )?;
1029 ///
1030 /// Ok(())
1031 /// }
1032 /// # Ruby::init(example).unwrap();
1033 /// ```
1034 fn writebarrier<T>(&self, young: T)
1035 where
1036 T: Mark,
1037 {
1038 unsafe { rb_gc_writebarrier(self.as_rb_value(), young.raw_with(&Ruby::get_with(*self))) };
1039 }
1040
1041 /// Opts `self` out of generational GC / write barrier protection.
1042 ///
1043 /// After calling this function `self` will not participate in generational
1044 /// GC and will always be scanned during a GC.
1045 /// See [`RTypedData::writebarrier`].
1046 fn writebarrier_unprotect<T, U>(&self) {
1047 unsafe { rb_gc_writebarrier_unprotect(self.as_rb_value()) };
1048 }
1049}
1050
1051impl Writebarrier for RTypedData {}
1052
1053impl<T> Writebarrier for Obj<T> where T: TypedData {}
1054
1055/// Trait for a Ruby-compatible `#hash` method.
1056///
1057/// Automatically implemented for any type implementing [`std::hash::Hash`].
1058///
1059/// See also [`Dup`], [`Inspect`], [`IsEql`], and [`typed_data::Cmp`](Cmp).
1060///
1061/// # Examples
1062///
1063/// ```
1064/// use std::hash::Hasher;
1065///
1066/// use magnus::{
1067/// DataTypeFunctions, Error, Ruby, TypedData, Value, function, gc, method, prelude::*,
1068/// typed_data, value::Opaque,
1069/// };
1070///
1071/// #[derive(TypedData)]
1072/// #[magnus(class = "Pair", free_immediately, mark)]
1073/// struct Pair {
1074/// #[magnus(opaque_attr_reader)]
1075/// a: Opaque<Value>,
1076/// #[magnus(opaque_attr_reader)]
1077/// b: Opaque<Value>,
1078/// }
1079///
1080/// impl Pair {
1081/// fn new(a: Value, b: Value) -> Self {
1082/// Self {
1083/// a: a.into(),
1084/// b: b.into(),
1085/// }
1086/// }
1087/// }
1088///
1089/// impl DataTypeFunctions for Pair {
1090/// fn mark(&self, marker: &gc::Marker) {
1091/// marker.mark(self.a);
1092/// marker.mark(self.b);
1093/// }
1094/// }
1095///
1096/// impl std::hash::Hash for Pair {
1097/// fn hash<H: Hasher>(&self, state: &mut H) {
1098/// state.write_i64(
1099/// self.a()
1100/// .hash()
1101/// .expect("#hash should not fail")
1102/// .to_i64()
1103/// .expect("#hash result guaranteed to be <= i64"),
1104/// );
1105/// state.write_i64(
1106/// self.b()
1107/// .hash()
1108/// .expect("#hash should not fail")
1109/// .to_i64()
1110/// .expect("#hash result guaranteed to be <= i64"),
1111/// );
1112/// }
1113/// }
1114///
1115/// impl PartialEq for Pair {
1116/// fn eq(&self, other: &Self) -> bool {
1117/// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false)
1118/// }
1119/// }
1120///
1121/// impl Eq for Pair {}
1122///
1123/// fn example(ruby: &Ruby) -> Result<(), Error> {
1124/// let class = ruby.define_class("Pair", ruby.class_object())?;
1125/// class.define_singleton_method("new", function!(Pair::new, 2))?;
1126/// class.define_method("hash", method!(<Pair as typed_data::Hash>::hash, 0))?;
1127/// class.define_method("eql?", method!(<Pair as typed_data::IsEql>::is_eql, 1))?;
1128///
1129/// let a = Pair::new(
1130/// ruby.str_new("foo").as_value(),
1131/// ruby.integer_from_i64(1).as_value(),
1132/// );
1133/// let hash = ruby.hash_new();
1134/// hash.aset(a, "test value")?;
1135///
1136/// let b = Pair::new(
1137/// ruby.str_new("foo").as_value(),
1138/// ruby.integer_from_i64(1).as_value(),
1139/// );
1140/// assert_eq!("test value", hash.fetch::<_, String>(b)?);
1141///
1142/// let c = Pair::new(
1143/// ruby.str_new("bar").as_value(),
1144/// ruby.integer_from_i64(2).as_value(),
1145/// );
1146/// assert!(hash.get(c).is_none());
1147///
1148/// Ok(())
1149/// }
1150/// # Ruby::init(example).unwrap()
1151/// ```
1152pub trait Hash {
1153 // Docs at trait level.
1154 #![allow(missing_docs)]
1155 fn hash(&self) -> i64;
1156}
1157
1158impl<T> Hash for T
1159where
1160 T: std::hash::Hash,
1161{
1162 fn hash(&self) -> i64 {
1163 let mut hasher = DefaultHasher::new();
1164 std::hash::Hash::hash(self, &mut hasher);
1165 // Ensure the Rust usize hash converts nicely to Ruby's expected range
1166 // if we return usize it'd truncate to 0 for anything negative.
1167 hasher.finish() as i64
1168 }
1169}
1170
1171/// Trait for a Ruby-compatible `#eql?` method.
1172///
1173/// Automatically implemented for any type implementing [`Eq`] and
1174/// [`TryConvert`].
1175///
1176/// See also [`Dup`], [`Inspect`], [`typed_data::Cmp`](Cmp), and
1177/// [`typed_data::Hash`](Hash).
1178///
1179/// # Examples
1180///
1181/// ```
1182/// use std::hash::Hasher;
1183///
1184/// use magnus::{
1185/// DataTypeFunctions, Error, Ruby, TypedData, Value, function, gc, method, prelude::*,
1186/// typed_data, value::Opaque,
1187/// };
1188///
1189/// #[derive(TypedData)]
1190/// #[magnus(class = "Pair", free_immediately, mark)]
1191/// struct Pair {
1192/// #[magnus(opaque_attr_reader)]
1193/// a: Opaque<Value>,
1194/// #[magnus(opaque_attr_reader)]
1195/// b: Opaque<Value>,
1196/// }
1197///
1198/// impl Pair {
1199/// fn new(a: Value, b: Value) -> Self {
1200/// Self {
1201/// a: a.into(),
1202/// b: b.into(),
1203/// }
1204/// }
1205/// }
1206///
1207/// impl DataTypeFunctions for Pair {
1208/// fn mark(&self, marker: &gc::Marker) {
1209/// marker.mark(self.a);
1210/// marker.mark(self.b);
1211/// }
1212/// }
1213///
1214/// impl std::hash::Hash for Pair {
1215/// fn hash<H: Hasher>(&self, state: &mut H) {
1216/// state.write_i64(
1217/// self.a()
1218/// .hash()
1219/// .expect("#hash should not fail")
1220/// .to_i64()
1221/// .expect("#hash result guaranteed to be <= i64"),
1222/// );
1223/// state.write_i64(
1224/// self.b()
1225/// .hash()
1226/// .expect("#hash should not fail")
1227/// .to_i64()
1228/// .expect("#hash result guaranteed to be <= i64"),
1229/// );
1230/// }
1231/// }
1232///
1233/// impl PartialEq for Pair {
1234/// fn eq(&self, other: &Self) -> bool {
1235/// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false)
1236/// }
1237/// }
1238///
1239/// impl Eq for Pair {}
1240///
1241/// fn example(ruby: &Ruby) -> Result<(), Error> {
1242/// let class = ruby.define_class("Pair", ruby.class_object())?;
1243/// class.define_singleton_method("new", function!(Pair::new, 2))?;
1244/// class.define_method("hash", method!(<Pair as typed_data::Hash>::hash, 0))?;
1245/// class.define_method("eql?", method!(<Pair as typed_data::IsEql>::is_eql, 1))?;
1246///
1247/// let a = Pair::new(
1248/// ruby.str_new("foo").as_value(),
1249/// ruby.integer_from_i64(1).as_value(),
1250/// );
1251/// let hash = ruby.hash_new();
1252/// hash.aset(a, "test value")?;
1253///
1254/// let b = Pair::new(
1255/// ruby.str_new("foo").as_value(),
1256/// ruby.integer_from_i64(1).as_value(),
1257/// );
1258/// assert_eq!("test value", hash.fetch::<_, String>(b)?);
1259///
1260/// let c = Pair::new(
1261/// ruby.str_new("bar").as_value(),
1262/// ruby.integer_from_i64(2).as_value(),
1263/// );
1264/// assert!(hash.get(c).is_none());
1265///
1266/// Ok(())
1267/// }
1268/// # Ruby::init(example).unwrap()
1269/// ```
1270pub trait IsEql {
1271 // Docs at trait level.
1272 #![allow(missing_docs)]
1273 fn is_eql(&self, other: Value) -> bool;
1274}
1275
1276impl<'a, T> IsEql for T
1277where
1278 T: Eq + 'a,
1279 &'a T: TryConvert,
1280{
1281 fn is_eql(&self, other: Value) -> bool {
1282 <&'a T>::try_convert(other)
1283 .map(|o| self == o)
1284 .unwrap_or(false)
1285 }
1286}
1287
1288/// Trait for a Ruby-compatible `#<=>` method.
1289///
1290/// Automatically implemented for any type implementing [`PartialOrd`] and
1291/// [`TryConvert`].
1292///
1293/// See also [`Dup`], [`Inspect`], [`IsEql`] and [`typed_data::Hash`](Hash).
1294///
1295/// # Examples
1296///
1297/// ```
1298/// use std::cmp::Ordering;
1299///
1300/// use magnus::{
1301/// DataTypeFunctions, Error, Module, Ruby, TypedData, Value, function, gc, method, prelude::*,
1302/// rb_assert, typed_data, value::Opaque,
1303/// };
1304///
1305/// #[derive(TypedData)]
1306/// #[magnus(class = "Pair", free_immediately, mark)]
1307/// struct Pair {
1308/// #[magnus(opaque_attr_reader)]
1309/// a: Opaque<Value>,
1310/// #[magnus(opaque_attr_reader)]
1311/// b: Opaque<Value>,
1312/// }
1313///
1314/// impl Pair {
1315/// fn new(a: Value, b: Value) -> Self {
1316/// Self {
1317/// a: a.into(),
1318/// b: b.into(),
1319/// }
1320/// }
1321/// }
1322///
1323/// impl DataTypeFunctions for Pair {
1324/// fn mark(&self, marker: &gc::Marker) {
1325/// marker.mark(self.a);
1326/// marker.mark(self.b);
1327/// }
1328/// }
1329///
1330/// impl PartialEq for Pair {
1331/// fn eq(&self, other: &Self) -> bool {
1332/// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false)
1333/// }
1334/// }
1335///
1336/// impl PartialOrd for Pair {
1337/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1338/// let a = self
1339/// .a()
1340/// .funcall("<=>", (other.a(),))
1341/// .ok()
1342/// .map(|o: i64| o.cmp(&0))?;
1343/// match a {
1344/// Ordering::Less | Ordering::Greater => Some(a),
1345/// Ordering::Equal => self
1346/// .b()
1347/// .funcall("<=>", (other.b(),))
1348/// .ok()
1349/// .map(|o: i64| o.cmp(&0)),
1350/// }
1351/// }
1352/// }
1353///
1354/// fn example(ruby: &Ruby) -> Result<(), Error> {
1355/// let class = ruby.define_class("Pair", ruby.class_object())?;
1356/// class.define_singleton_method("new", function!(Pair::new, 2))?;
1357/// class.define_method("<=>", method!(<Pair as typed_data::Cmp>::cmp, 1))?;
1358/// class.include_module(ruby.module_comparable())?;
1359///
1360/// let a = Pair::new(
1361/// ruby.str_new("foo").as_value(),
1362/// ruby.integer_from_i64(1).as_value(),
1363/// );
1364/// let b = Pair::new(
1365/// ruby.str_new("foo").as_value(),
1366/// ruby.integer_from_i64(2).as_value(),
1367/// );
1368/// rb_assert!(ruby, "a < b", a, b);
1369///
1370/// let b = Pair::new(
1371/// ruby.str_new("foo").as_value(),
1372/// ruby.integer_from_i64(2).as_value(),
1373/// );
1374/// let c = Pair::new(
1375/// ruby.str_new("bar").as_value(),
1376/// ruby.integer_from_i64(1).as_value(),
1377/// );
1378/// rb_assert!(ruby, "b > c", b, c);
1379///
1380/// let a = Pair::new(
1381/// ruby.str_new("foo").as_value(),
1382/// ruby.integer_from_i64(1).as_value(),
1383/// );
1384/// let b = Pair::new(
1385/// ruby.str_new("foo").as_value(),
1386/// ruby.integer_from_i64(2).as_value(),
1387/// );
1388/// rb_assert!(ruby, "(a <=> b) == -1", a, b);
1389///
1390/// Ok(())
1391/// }
1392/// # Ruby::init(example).unwrap()
1393/// ```
1394pub trait Cmp {
1395 // Docs at trait level.
1396 #![allow(missing_docs)]
1397 fn cmp(&self, other: Value) -> Option<i64>;
1398}
1399
1400impl<'a, T> Cmp for T
1401where
1402 T: PartialOrd + 'a,
1403 &'a T: TryConvert,
1404{
1405 fn cmp(&self, other: Value) -> Option<i64> {
1406 <&'a T>::try_convert(other)
1407 .ok()
1408 .and_then(|o| self.partial_cmp(o))
1409 .map(|o| o as i64)
1410 }
1411}
1412
1413/// Trait for a Ruby-compatible `#inspect` method.
1414///
1415/// Automatically implemented for any type implementing [`Debug`].
1416///
1417/// See also [`Dup`], [`IsEql`], [`typed_data::Cmp`](Cmp), and
1418/// [`typed_data::Hash`](Hash).
1419///
1420/// # Examples
1421///
1422/// ```
1423/// use std::fmt;
1424///
1425/// use magnus::{
1426/// DataTypeFunctions, Error, Ruby, TypedData, Value, function, gc, method, prelude::*,
1427/// rb_assert, typed_data, value::Opaque,
1428/// };
1429///
1430/// #[derive(TypedData)]
1431/// #[magnus(class = "Pair", free_immediately, mark)]
1432/// struct Pair {
1433/// #[magnus(opaque_attr_reader)]
1434/// a: Opaque<Value>,
1435/// #[magnus(opaque_attr_reader)]
1436/// b: Opaque<Value>,
1437/// }
1438///
1439/// impl Pair {
1440/// fn new(a: Value, b: Value) -> Self {
1441/// Self {
1442/// a: a.into(),
1443/// b: b.into(),
1444/// }
1445/// }
1446/// }
1447///
1448/// impl DataTypeFunctions for Pair {
1449/// fn mark(&self, marker: &gc::Marker) {
1450/// marker.mark(self.a);
1451/// marker.mark(self.b);
1452/// }
1453/// }
1454///
1455/// impl fmt::Debug for Pair {
1456/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1457/// f.debug_struct("Pair")
1458/// .field("a", &self.a())
1459/// .field("b", &self.b())
1460/// .finish()
1461/// }
1462/// }
1463///
1464/// fn example(ruby: &Ruby) -> Result<(), Error> {
1465/// let class = ruby.define_class("Pair", ruby.class_object())?;
1466/// class.define_singleton_method("new", function!(Pair::new, 2))?;
1467/// class.define_method(
1468/// "inspect",
1469/// method!(<Pair as typed_data::Inspect>::inspect, 0),
1470/// )?;
1471///
1472/// let pair = Pair::new(
1473/// ruby.str_new("foo").as_value(),
1474/// ruby.integer_from_i64(1).as_value(),
1475/// );
1476/// rb_assert!(ruby, r#"pair.inspect == "Pair { a: \"foo\", b: 1 }""#, pair);
1477///
1478/// Ok(())
1479/// }
1480/// # Ruby::init(example).unwrap()
1481/// ```
1482pub trait Inspect {
1483 // Docs at trait level.
1484 #![allow(missing_docs)]
1485 fn inspect(&self) -> String;
1486}
1487
1488impl<T> Inspect for T
1489where
1490 T: fmt::Debug,
1491{
1492 fn inspect(&self) -> String {
1493 format!("{:?}", self)
1494 }
1495}
1496
1497/// Trait for a Ruby-compatible `#dup` and `#clone` methods.
1498///
1499/// Automatically implemented for any type implementing [`Clone`].
1500///
1501/// See also [`Inspect`], [`IsEql`], [`typed_data::Cmp`](Cmp), and
1502/// [`typed_data::Hash`](Hash).
1503///
1504/// # Examples
1505///
1506/// ```
1507/// use magnus::{
1508/// DataTypeFunctions, Error, Ruby, TypedData, Value, function, gc, method, prelude::*,
1509/// rb_assert, typed_data, value::Opaque,
1510/// };
1511///
1512/// #[derive(TypedData, Clone)]
1513/// #[magnus(class = "Pair", free_immediately, mark)]
1514/// struct Pair {
1515/// a: Opaque<Value>,
1516/// b: Opaque<Value>,
1517/// }
1518///
1519/// impl Pair {
1520/// fn new(a: Value, b: Value) -> Self {
1521/// Self {
1522/// a: a.into(),
1523/// b: b.into(),
1524/// }
1525/// }
1526/// }
1527///
1528/// impl DataTypeFunctions for Pair {
1529/// fn mark(&self, marker: &gc::Marker) {
1530/// marker.mark(self.a);
1531/// marker.mark(self.b);
1532/// }
1533/// }
1534///
1535/// fn example(ruby: &Ruby) -> Result<(), Error> {
1536/// let class = ruby.define_class("Pair", ruby.class_object())?;
1537/// class.define_singleton_method("new", function!(Pair::new, 2))?;
1538/// class.define_method("dup", method!(<Pair as typed_data::Dup>::dup, 0))?;
1539/// class.define_method("clone", method!(<Pair as typed_data::Dup>::clone, -1))?;
1540///
1541/// let a = Pair::new(
1542/// ruby.str_new("foo").as_value(),
1543/// ruby.integer_from_i64(1).as_value(),
1544/// );
1545/// rb_assert!(ruby, "b = a.dup; a.object_id != b.object_id", a);
1546///
1547/// Ok(())
1548/// }
1549/// # Ruby::init(example).unwrap()
1550/// ```
1551pub trait Dup: Sized {
1552 // Docs at trait level.
1553 #![allow(missing_docs)]
1554 fn dup(&self) -> Self;
1555 fn clone(rbself: Obj<Self>, args: &[Value]) -> Result<Obj<Self>, Error>;
1556}
1557
1558impl<T> Dup for T
1559where
1560 T: Clone + TypedData,
1561{
1562 fn dup(&self) -> Self {
1563 self.clone()
1564 }
1565
1566 fn clone(rbself: Obj<Self>, args: &[Value]) -> Result<Obj<Self>, Error> {
1567 let args = scan_args::<(), (), (), (), _, ()>(args)?;
1568 let kwargs =
1569 get_kwargs::<_, (), (Option<Option<bool>>,), ()>(args.keywords, &[], &["freeze"])?;
1570 let (freeze,) = kwargs.optional;
1571 let freeze = freeze.flatten();
1572
1573 let clone = Ruby::get_with(rbself).obj_wrap((*rbself).clone());
1574 let class_clone = unsafe { rb_singleton_class_clone(rbself.as_rb_value()) };
1575 unsafe { rb_obj_reveal(clone.as_rb_value(), class_clone) };
1576 unsafe { rb_singleton_class_attached(class_clone, clone.as_rb_value()) };
1577 match freeze {
1578 Some(true) => clone.freeze(),
1579 None if rbself.is_frozen() => clone.freeze(),
1580 _ => (),
1581 }
1582 Ok(clone)
1583 }
1584}