glib/subclass/
boxed.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3// rustdoc-stripper-ignore-next
4//! Module for registering boxed types for Rust types.
5
6use crate::{ffi, gobject_ffi, prelude::*, translate::*};
7
8// rustdoc-stripper-ignore-next
9/// Trait for defining boxed types.
10///
11/// Links together the type name with the type itself.
12///
13/// See [`register_boxed_type`] for registering an implementation of this trait
14/// with the type system.
15///
16/// [`register_boxed_type`]: fn.register_boxed_type.html
17pub trait BoxedType: StaticType + Clone + Sized + 'static {
18    // rustdoc-stripper-ignore-next
19    /// Boxed type name.
20    ///
21    /// This must be unique in the whole process.
22    const NAME: &'static str;
23
24    // rustdoc-stripper-ignore-next
25    /// Allow name conflicts for this boxed type.
26    ///
27    /// By default, trying to register a type with a name that was registered before will panic. If
28    /// this is set to `true` then a new name will be selected by appending a counter.
29    ///
30    /// This is useful for defining new types in Rust library crates that might be linked multiple
31    /// times in the same process.
32    ///
33    /// A consequence of setting this to `true` is that it's not guaranteed that
34    /// `glib::Type::from_name(Self::NAME).unwrap() == Self::static_type()`.
35    ///
36    /// Optional.
37    const ALLOW_NAME_CONFLICT: bool = false;
38}
39
40// rustdoc-stripper-ignore-next
41/// Register a boxed `glib::Type` ID for `T`.
42///
43/// This must be called only once and will panic on a second call.
44///
45/// See [`Boxed!`] for defining a function that ensures that
46/// this is only called once and returns the type id.
47///
48/// [`Boxed!`]: ../../derive.Boxed.html
49pub fn register_boxed_type<T: BoxedType>() -> crate::Type {
50    unsafe extern "C" fn boxed_copy<T: BoxedType>(v: ffi::gpointer) -> ffi::gpointer {
51        unsafe {
52            let v = &*(v as *mut T);
53            let copy = Box::new(v.clone());
54
55            Box::into_raw(copy) as ffi::gpointer
56        }
57    }
58    unsafe extern "C" fn boxed_free<T: BoxedType>(v: ffi::gpointer) {
59        unsafe {
60            let v = v as *mut T;
61            let _ = Box::from_raw(v);
62        }
63    }
64    unsafe {
65        use std::ffi::CString;
66
67        let type_name = if T::ALLOW_NAME_CONFLICT {
68            let mut i = 0;
69            loop {
70                let type_name = CString::new(if i == 0 {
71                    T::NAME.to_string()
72                } else {
73                    format!("{}-{}", T::NAME, i)
74                })
75                .unwrap();
76                if gobject_ffi::g_type_from_name(type_name.as_ptr()) == gobject_ffi::G_TYPE_INVALID
77                {
78                    break type_name;
79                }
80                i += 1;
81            }
82        } else {
83            let type_name = CString::new(T::NAME).unwrap();
84            assert_eq!(
85                gobject_ffi::g_type_from_name(type_name.as_ptr()),
86                gobject_ffi::G_TYPE_INVALID,
87                "Type {} has already been registered",
88                type_name.to_str().unwrap()
89            );
90
91            type_name
92        };
93
94        let type_ = crate::Type::from_glib(gobject_ffi::g_boxed_type_register_static(
95            type_name.as_ptr(),
96            Some(boxed_copy::<T>),
97            Some(boxed_free::<T>),
98        ));
99        assert!(type_.is_valid());
100
101        type_
102    }
103}
104
105#[cfg(test)]
106mod test {
107    // We rename the current crate as glib, since the macros in glib-macros
108    // generate the glib namespace through the crate_ident_new utility,
109    // and that returns `glib` (and not `crate`) when called inside the glib crate
110    use crate as glib;
111    use crate::prelude::*;
112    use crate::translate::{FromGlibPtrBorrow, FromGlibPtrFull, IntoGlibPtr};
113
114    #[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
115    #[boxed_type(name = "MyBoxed")]
116    struct MyBoxed(String);
117
118    #[test]
119    fn test_register() {
120        assert!(MyBoxed::static_type().is_valid());
121    }
122
123    #[test]
124    fn test_value() {
125        assert!(MyBoxed::static_type().is_valid());
126
127        let b = MyBoxed(String::from("abc"));
128        let v = b.to_value();
129        let b2 = v.get::<&MyBoxed>().unwrap();
130        assert_eq!(&b, b2);
131    }
132
133    #[test]
134    fn test_from_glib_borrow() {
135        assert!(MyBoxed::static_type().is_valid());
136
137        let b = MyBoxed(String::from("abc"));
138        let raw_ptr = MyBoxed::into_glib_ptr(b);
139
140        // test that the from_glib_borrow does not take ownership of the raw_ptr
141        let _ = unsafe { MyBoxed::from_glib_borrow(raw_ptr) };
142
143        let new_b = unsafe { MyBoxed::from_glib_full(raw_ptr) };
144
145        assert_eq!(new_b.0, "abc".to_string());
146    }
147}