diff --git a/objc2/src/rc/mod.rs b/objc2/src/rc/mod.rs index 317a30c42..c760fd8db 100644 --- a/objc2/src/rc/mod.rs +++ b/objc2/src/rc/mod.rs @@ -39,10 +39,14 @@ assert!(weak.load().is_null()); */ mod autorelease; +mod owned; +mod retained; mod strong; mod weak; pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe}; +pub use self::owned::Owned; +pub use self::retained::Retained; pub use self::strong::StrongPtr; pub use self::weak::WeakPtr; @@ -53,23 +57,6 @@ mod tests { use super::StrongPtr; use crate::runtime::Object; - #[test] - fn test_strong_clone() { - fn retain_count(obj: *mut Object) -> usize { - unsafe { msg_send![obj, retainCount] } - } - - let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; - assert!(retain_count(*obj) == 1); - - let cloned = obj.clone(); - assert!(retain_count(*cloned) == 2); - assert!(retain_count(*obj) == 2); - - drop(obj); - assert!(retain_count(*cloned) == 1); - } - #[test] fn test_weak() { let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; diff --git a/objc2/src/rc/owned.rs b/objc2/src/rc/owned.rs new file mode 100644 index 000000000..37117da86 --- /dev/null +++ b/objc2/src/rc/owned.rs @@ -0,0 +1,219 @@ +use core::borrow; +use core::fmt; +use core::hash; +use core::marker::PhantomData; +use core::mem; +use core::ops::{Deref, DerefMut}; +use core::ptr::{drop_in_place, NonNull}; + +use super::AutoreleasePool; +use super::Retained; + +/// A smart pointer that strongly references and uniquely owns an Objective-C +/// object. +/// +/// The fact that we uniquely own the pointer means that it's safe to mutate +/// it. As such, this implements [`DerefMut`]. +/// +/// This is guaranteed to have the same size as the underlying pointer. +/// +/// # Cloning and [`Retained`] +/// +/// This does not implement [`Clone`], but [`Retained`] has a [`From`] +/// implementation to convert from this, so you can easily reliquish ownership +/// and work with a clonable [`Retained`] pointer. +/// +/// ```no_run +/// let obj: Owned = ...; +/// let retained: Retained = obj.into(); +/// let cloned: Retained = retained.clone(); +/// ``` +/// +/// TODO: Explain similarities to [`Box`]. +/// +/// TODO: Explain this vs. [`Retained`] +#[repr(transparent)] +pub struct Owned { + /// The pointer is always retained. + ptr: NonNull, // We are the unique owner of T, so covariance is correct + phantom: PhantomData, // Necessary for dropck +} + +/// `Owned` pointers are `Send` if `T` is `Send` because they give the same +/// access as having a T directly. +unsafe impl Send for Owned {} + +/// `Owned` pointers are `Sync` if `T` is `Sync` because they give the same +/// access as having a `T` directly. +unsafe impl Sync for Owned {} + +// TODO: Unsure how the API should look... +impl Owned { + /// Create a new `Owned` pointer to the object. + /// + /// Uses a retain count that has been handed off from somewhere else, + /// usually Objective-C methods like `init`, `alloc`, `new`, or `copy`. + /// + /// # Safety + /// + /// The caller must ensure that there are no other pointers or references + /// to the same object, and the given pointer is not be used afterwards. + /// + /// Additionally, the given object pointer must have +1 retain count. + /// + /// And lastly, the object pointer must be valid as a mutable reference + /// (non-null, aligned, dereferencable, initialized and upholds aliasing + /// rules, see the [`std::ptr`] module for more information). + /// + /// # Example + /// + /// ```rust + /// let obj: &mut Object = unsafe { msg_send![cls, alloc] }; + /// let obj: Owned = unsafe { Owned::new(msg_send![obj, init]) }; + /// // Or in this case simply just: + /// let obj: Owned = unsafe { Owned::new(msg_send![cls, new]) }; + /// ``` + #[inline] + // Note: We don't take a mutable reference as a parameter since it would + // be too easy to accidentally create two aliasing mutable references. + pub unsafe fn new(ptr: *mut T) -> Self { + Self { + // SAFETY: Upheld by the caller + ptr: NonNull::new_unchecked(ptr), + phantom: PhantomData, + } + } + + /// Acquires a `*mut` pointer to the object. + #[inline] + pub fn as_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } + + /// Construct an `Owned` pointer from a `Retained` pointer. + /// + /// # Safety + /// + /// The caller must ensure that there are no other pointers to the same + /// object (which also means that the given [`Retained`] should have a + /// retain count of exactly 1 in almost all cases). + #[inline] + pub unsafe fn from_retained(obj: Retained) -> Self { + // SAFETY: The pointer is guaranteed by `Retained` to be NonNull + let ptr = NonNull::new_unchecked(mem::ManuallyDrop::new(obj).as_ptr() as *mut T); + Self { + ptr, + phantom: PhantomData, + } + } + + /// Autoreleases the retained pointer, meaning that the object is not + /// immediately released, but will be when the innermost / current + /// autorelease pool is drained. + #[doc(alias = "objc_autorelease")] + #[must_use = "If you don't intend to use the object any more, just drop it as usual"] + #[inline] + pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T { + let retained: Retained = self.into(); + let ptr = retained.autorelease(pool) as *const T as *mut T; + // SAFETY: The pointer was previously `Owned`, so is safe to be mutable + unsafe { &mut *ptr } + } +} + +/// `#[may_dangle]` (see [this][dropck_eyepatch]) would not be safe here, +/// since we cannot verify that a `dealloc` method doesn't access borrowed +/// data. +/// +/// [dropck_eyepatch]: https://doc.rust-lang.org/nightly/nomicon/dropck.html#an-escape-hatch +impl Drop for Owned { + /// Releases the retained object. + /// + /// This is guaranteed to be the last destructor that runs, in contrast to + /// [`Retained`], which means that we can run the [`Drop`] implementation + /// on the contained object as well. + #[inline] + fn drop(&mut self) { + let ptr = self.as_ptr(); + unsafe { + drop_in_place(ptr); + // Construct a new `Retained`, which will be dropped immediately + Retained::new(ptr); + }; + } +} + +impl Deref for Owned { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: TODO + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for Owned { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: TODO + unsafe { self.ptr.as_mut() } + } +} + +// TODO: impl PartialEq, PartialOrd, Ord and Eq + +impl fmt::Display for Owned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl fmt::Debug for Owned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Pointer for Owned { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Pointer::fmt(&self.as_ptr(), f) + } +} + +impl hash::Hash for Owned { + fn hash(&self, state: &mut H) { + (&**self).hash(state) + } +} + +// TODO: impl Fn traits? See `boxed_closure_impls` + +// TODO: CoerceUnsized + +impl borrow::Borrow for Owned { + fn borrow(&self) -> &T { + &**self + } +} + +impl borrow::BorrowMut for Owned { + fn borrow_mut(&mut self) -> &mut T { + &mut **self + } +} + +impl AsRef for Owned { + fn as_ref(&self) -> &T { + &**self + } +} + +impl AsMut for Owned { + fn as_mut(&mut self) -> &mut T { + &mut **self + } +} + +// TODO: Comment on impl Unpin for Box +impl Unpin for Owned {} diff --git a/objc2/src/rc/retained.rs b/objc2/src/rc/retained.rs new file mode 100644 index 000000000..1db7258d6 --- /dev/null +++ b/objc2/src/rc/retained.rs @@ -0,0 +1,359 @@ +use core::borrow; +use core::fmt; +use core::hash; +use core::marker::{PhantomData, Unpin}; +use core::mem; +use core::ops::Deref; +use core::ptr::NonNull; + +use super::AutoreleasePool; +use super::Owned; +use crate::runtime::{self, Object}; + +/// An smart pointer that strongly references an object, ensuring it won't be +/// deallocated. +/// +/// This doesn't own the object, so it is not safe to obtain a mutable +/// reference from this. For that, see [`Owned`]. +/// +/// This is guaranteed to have the same size as the underlying pointer. +/// +/// TODO: Something about the fact that we haven't made the methods associated +/// for [reasons]??? +/// +/// ## Caveats +/// +/// If the inner type implements [`Drop`], that implementation will not be +/// called, since there is no way to ensure that the Objective-C runtime will +/// do so. If you need to run some code when the object is destroyed, +/// implement the `dealloc` selector instead. +/// +/// TODO: Restrict the possible types with some kind of unsafe marker trait? +/// +/// TODO: Explain similarities with `Arc` and `RefCell`. +#[repr(transparent)] +pub struct Retained { + /// A pointer to the contained object. + /// + /// It is important that this is `NonNull`, since we want to dereference + /// it later. + /// + /// Usually the contained object would be an [extern type][extern-type-rfc] + /// (when that gets stabilized), or a type such as: + /// ``` + /// pub struct MyType { + /// _data: [u8; 0], // TODO: `UnsafeCell`? + /// } + /// ``` + /// + /// DSTs that carry metadata cannot be used here, so unsure if we should + /// have a `?Sized` bound? + /// + /// TODO: + /// https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait + /// https://doc.rust-lang.org/nomicon/exotic-sizes.html + /// https://doc.rust-lang.org/core/ptr/trait.Pointee.html + /// https://doc.rust-lang.org/core/ptr/traitalias.Thin.html + /// + /// [extern-type-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1861-extern-types.md + ptr: NonNull, // T is immutable, so covariance is correct + /// TODO: + /// https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data + phantom: PhantomData, +} + +/// The `Send` implementation requires `T: Sync` because `Retained` gives +/// access to `&T`. +/// +/// Additiontally, it requires `T: Send` because if `T: !Send`, you could +/// clone a `Retained`, send it to another thread, and drop the clone last, +/// making `dealloc` get called on the other thread, violating `T: !Send`. +unsafe impl Send for Retained {} + +/// The `Sync` implementation requires `T: Sync` because `&Retained` gives +/// access to `&T`. +/// +/// Additiontally, it requires `T: Send`, because if `T: !Send`, you could +/// clone a `&Retained` from another thread, and drop the clone last, making +/// `dealloc` get called on the other thread, violating `T: !Send`. +unsafe impl Sync for Retained {} + +impl Retained { + /// Constructs a `Retained` to an object that already has a +1 retain + /// count. This will not retain the object. + /// + /// When dropped, the object will be released. + /// + /// This is used when you have a retain count that has been handed off + /// from somewhere else, usually Objective-C methods with the + /// `ns_returns_retained` attribute. See [`Owned::new`] for the more + /// common case when creating objects. + /// + /// # Safety + /// + /// The caller must ensure the given object reference has +1 retain count. + /// + /// Additionally, there must be no [`Owned`] pointers or mutable + /// references to the same object. + /// + /// And lastly, the object pointer must be valid as a reference (non-null, + /// aligned, dereferencable, initialized and upholds aliasing rules, see + /// the [`std::ptr`] module for more information). + #[inline] + pub unsafe fn new(ptr: *const T) -> Self { + Self { + // SAFETY: Upheld by the caller + ptr: NonNull::new_unchecked(ptr as *mut T), + phantom: PhantomData, + } + } + + /// Acquires a `*const` pointer to the object. + #[inline] + pub fn as_ptr(&self) -> *const T { + self.ptr.as_ptr() + } + + /// Retains the given object pointer. + /// + /// When dropped, the object will be released. + /// + /// # Safety + /// + /// The caller must ensure that there are no [`Owned`] pointers to the + /// same object. + /// + /// Additionally, the object pointer must be valid as a reference + /// (non-null, aligned, dereferencable, initialized and upholds aliasing + /// rules, see the [`std::ptr`] module for more information). + // + // So this would be illegal: + // ```rust + // let owned: Owned = ...; + // // Lifetime information is discarded + // let retained = Retained::retain(&*owned); + // // Which means we can still mutate `Owned`: + // let x: &mut T = &mut *owned; + // // While we have an immutable reference + // let y: &T = &*retained; + // ``` + #[doc(alias = "objc_retain")] + // Inlined since it's `objc_retain` that does the work. + #[cfg_attr(debug_assertions, inline)] + pub unsafe fn retain(ptr: *const T) -> Self { + // SAFETY: The caller upholds that the pointer is valid + let rtn = runtime::objc_retain(ptr as *mut Object) as *const T; + debug_assert_eq!(rtn, ptr); + Self { + // SAFETY: Non-null upheld by the caller and `objc_retain` always + // returns the same pointer. + ptr: NonNull::new_unchecked(rtn as *mut T), + phantom: PhantomData, + } + } + + /// TODO + #[doc(alias = "objc_retainAutoreleasedReturnValue")] + pub unsafe fn retain_autoreleased_return(_obj: *const T) -> Self { + todo!() + } + + /// Autoreleases the retained pointer, meaning that the object is not + /// immediately released, but will be when the innermost / current + /// autorelease pool is drained. + #[doc(alias = "objc_autorelease")] + #[must_use = "If you don't intend to use the object any more, just drop it as usual"] + #[inline] + pub fn autorelease<'p>(self, _pool: &'p AutoreleasePool) -> &'p T { + let ptr = mem::ManuallyDrop::new(self).as_ptr(); + // SAFETY: The `ptr` is guaranteed to be valid and have at least one + // retain count. + // And because of the ManuallyDrop, we don't call the Drop + // implementation, so the object won't also be released there. + unsafe { runtime::objc_autorelease(ptr as *mut Object) }; + // SAFETY: The lifetime is bounded by the type function signature + unsafe { &*ptr } + } + + /// TODO + #[doc(alias = "objc_autoreleaseReturnValue")] + pub fn autorelease_return<'p>(self, _pool: &'p AutoreleasePool) -> &'p T { + todo!() + } + + /// TODO + /// + /// Equivalent to `Retained::retain(&obj).autorelease(pool)`, but slightly + /// more efficient. + #[doc(alias = "objc_retainAutorelease")] + pub unsafe fn retain_and_autorelease<'p>(_obj: *const T, _pool: &'p AutoreleasePool) -> &'p T { + todo!() + } + + /// TODO + /// + /// Equivalent to `Retained::retain(&obj).autorelease_return(pool)`, but + /// slightly more efficient. + #[doc(alias = "objc_retainAutoreleaseReturnValue")] + pub unsafe fn retain_and_autorelease_return<'p>( + _obj: *const T, + _pool: &'p AutoreleasePool, + ) -> &'p T { + todo!() + } + + #[cfg(test)] // TODO + #[doc(alias = "retainCount")] + pub fn retain_count(&self) -> usize { + unsafe { msg_send![self.as_ptr() as *mut Object, retainCount] } + } +} + +// TODO: Consider something like this +// #[cfg(block)] +// impl Retained { +// #[doc(alias = "objc_retainBlock")] +// pub unsafe fn retain_block(block: &T) -> Self { +// todo!() +// } +// } + +/// `#[may_dangle]` (see [this][dropck_eyepatch]) doesn't really make sense +/// here, since we actually want to disallow creating `Retained` pointers to +/// objects that have a `Drop` implementation. +/// +/// [dropck_eyepatch]: https://doc.rust-lang.org/nightly/nomicon/dropck.html#an-escape-hatch +impl Drop for Retained { + /// Releases the retained object + #[doc(alias = "objc_release")] + #[doc(alias = "release")] + #[inline] + fn drop(&mut self) { + // SAFETY: The `ptr` is guaranteed to be valid and have at least one + // retain count + unsafe { runtime::objc_release(self.as_ptr() as *mut Object) }; + } +} + +impl Clone for Retained { + /// Makes a clone of the `Retained` object. + /// + /// This increases the object's reference count. + #[doc(alias = "objc_retain")] + #[doc(alias = "retain")] + #[inline] + fn clone(&self) -> Self { + // SAFETY: The `ptr` is guaranteed to be valid + unsafe { Self::retain(self.as_ptr()) } + } +} + +impl Deref for Retained { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: TODO + unsafe { self.ptr.as_ref() } + } +} + +impl PartialEq for Retained { + #[inline] + fn eq(&self, other: &Self) -> bool { + &**self == &**other + } + + #[inline] + fn ne(&self, other: &Self) -> bool { + &**self != &**other + } +} + +// TODO: impl PartialOrd, Ord and Eq + +impl fmt::Display for Retained { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl fmt::Debug for Retained { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Pointer for Retained { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Pointer::fmt(&self.as_ptr(), f) + } +} + +impl hash::Hash for Retained { + fn hash(&self, state: &mut H) { + (&**self).hash(state) + } +} + +impl borrow::Borrow for Retained { + fn borrow(&self) -> &T { + &**self + } +} + +impl AsRef for Retained { + fn as_ref(&self) -> &T { + &**self + } +} + +// TODO: CoerceUnsized? + +impl Unpin for Retained {} + +impl From> for Retained { + #[inline] + fn from(obj: Owned) -> Self { + let ptr = mem::ManuallyDrop::new(obj).as_ptr(); + // SAFETY: TODO + unsafe { Self::new(ptr) } + } +} + +#[cfg(test)] +mod tests { + use core::mem::size_of; + + use super::Retained; + use crate::runtime::Object; + + pub struct TestType { + _data: [u8; 0], // TODO: `UnsafeCell`? + } + + #[test] + fn test_size_of() { + assert_eq!(size_of::>(), size_of::<&TestType>()); + assert_eq!( + size_of::>>(), + size_of::<&TestType>() + ); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[test] + fn test_clone() { + // TODO: Maybe make a way to return `Retained` directly? + let obj: &Object = unsafe { msg_send![class!(NSObject), new] }; + let obj: Retained = unsafe { Retained::new(obj) }; + assert!(obj.retain_count() == 1); + + let cloned = obj.clone(); + assert!(cloned.retain_count() == 2); + assert!(obj.retain_count() == 2); + + drop(obj); + assert!(cloned.retain_count() == 1); + } +}