cairo/user_data.rs
1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::marker::PhantomData;
4
5use crate::ffi::cairo_user_data_key_t;
6
7pub struct UserDataKey<T> {
8 pub(crate) ffi: cairo_user_data_key_t,
9 marker: PhantomData<*const T>,
10}
11
12unsafe impl<T> Sync for UserDataKey<T> {}
13
14impl<T> UserDataKey<T> {
15 pub const fn new() -> Self {
16 Self {
17 ffi: cairo_user_data_key_t { unused: 0 },
18 marker: PhantomData,
19 }
20 }
21}
22
23// In a safe API for user data we can’t make `get_user_data`
24// transfer full ownership of the value to the caller (e.g. by returning `Box<T>`)
25// because `self` still has a pointer to that value
26// and `get_user_data` could be called again with the same key.
27//
28// We also can’t return a `&T` reference that borrows from `self`
29// because the value could be removed with `remove_user_data` or replaced with `set_user_data`
30// while the borrow still needs to be valid.
31// (Borrowing with `&mut self` would not help as `Self` can be itself reference-counted.)
32//
33// Therefore, the value must be reference-counted.
34//
35// We use `Rc` over `Arc` because the types implementing these methods are `!Send` and `!Sync`.
36// See <https://github.com/gtk-rs/cairo/issues/256>
37
38macro_rules! user_data_methods {
39 ($ffi_get_user_data: path, $ffi_set_user_data: path,) => {
40 /// Attach user data to `self` for the given `key`.
41 pub fn set_user_data<T: 'static>(
42 &self,
43 key: &'static crate::UserDataKey<T>,
44 value: std::rc::Rc<T>,
45 ) -> Result<(), crate::Error> {
46 unsafe extern "C" fn destructor<T>(ptr: *mut libc::c_void) {
47 let ptr: *const T = ptr as _;
48 drop(std::rc::Rc::from_raw(ptr))
49 }
50 // Safety:
51 //
52 // The destructor’s cast and `from_raw` are symmetric
53 // with the `into_raw` and cast below.
54 // They both transfer ownership of one strong reference:
55 // neither of them touches the reference count.
56 let ptr: *const T = std::rc::Rc::into_raw(value);
57 let ptr = ptr as *mut T as *mut libc::c_void;
58 let status = unsafe {
59 $ffi_set_user_data(self.to_raw_none(), &key.ffi, ptr, Some(destructor::<T>))
60 };
61 crate::utils::status_to_result(status)
62 }
63
64 /// Return the user data previously attached to `self` with the given `key`, if any.
65 pub fn user_data<T: 'static>(
66 &self,
67 key: &'static crate::UserDataKey<T>,
68 ) -> Option<std::rc::Rc<T>> {
69 let ptr = self.user_data_ptr(key)?.as_ptr();
70
71 // Safety:
72 //
73 // `Rc::from_raw` would normally take ownership of a strong reference for this pointer.
74 // But `self` still has a copy of that pointer and `get_user_data` can be called again
75 // with the same key.
76 // We use `ManuallyDrop` to avoid running the destructor of that first `Rc`,
77 // and return a cloned one (which increments the reference count).
78 unsafe {
79 let rc = std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(ptr));
80 Some(std::rc::Rc::clone(&rc))
81 }
82 }
83
84 /// Return the user data previously attached to `self` with the given `key`, if any,
85 /// without incrementing the reference count.
86 ///
87 /// The pointer is valid when it is returned from this method,
88 /// until the cairo object that `self` represents is destroyed
89 /// or `remove_user_data` or `set_user_data` is called with the same key.
90 pub fn user_data_ptr<T: 'static>(
91 &self,
92 key: &'static crate::UserDataKey<T>,
93 ) -> Option<std::ptr::NonNull<T>> {
94 // Safety:
95 //
96 // If `ffi_get_user_data` returns a non-null pointer,
97 // there was a previous call to `ffi_set_user_data` with a key with the same address.
98 // Either:
99 //
100 // * This was a call to a Rust `Self::set_user_data` method.
101 // Because that method takes a `&'static` reference,
102 // the key used then must live at that address until the end of the process.
103 // Because `UserDataKey<T>` has a non-zero size regardless of `T`,
104 // no other `UserDataKey<U>` value can have the same address.
105 // Therefore, the `T` type was the same then at it is now and `cast` is type-safe.
106 //
107 // * Or, it is technically possible that the `set` call was to the C function directly,
108 // with a `cairo_user_data_key_t` in heap-allocated memory that was then freed,
109 // then `Box::new(UserDataKey::new()).leak()` was used to create a `&'static`
110 // that happens to have the same address because the allocator for `Box`
111 // reused that memory region.
112 // Since this involves a C (or FFI) call *and* is so far out of “typical” use
113 // of the user data functionality, we consider this a misuse of an unsafe API.
114 unsafe {
115 let ptr = $ffi_get_user_data(self.to_raw_none(), &key.ffi);
116 Some(std::ptr::NonNull::new(ptr)?.cast())
117 }
118 }
119
120 /// Unattached from `self` the user data associated with `key`, if any.
121 /// If there is no other `Rc` strong reference, the data is destroyed.
122 pub fn remove_user_data<T: 'static>(
123 &self,
124 key: &'static crate::UserDataKey<T>,
125 ) -> Result<(), crate::Error> {
126 let status = unsafe {
127 $ffi_set_user_data(self.to_raw_none(), &key.ffi, std::ptr::null_mut(), None)
128 };
129 crate::utils::status_to_result(status)
130 }
131 };
132}