Skip to content

Commit 9eb1625

Browse files
committed
Add support for storing Id and Box in ivars
1 parent 2889cbe commit 9eb1625

File tree

7 files changed

+586
-51
lines changed

7 files changed

+586
-51
lines changed

objc2/CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88
## Unreleased - YYYY-MM-DD
99

1010
### Added
11-
* `Ivar::write`, `Ivar::as_ptr` and `Ivar::as_mut_ptr` for querying/modifying
12-
the instance variable inside `init` methods.
11+
* Added `Ivar::write`, `Ivar::as_ptr` and `Ivar::as_mut_ptr` for safely
12+
querying and modifying instance variables inside `init` methods.
13+
* Added `IvarDrop<T>` to allow storing complex `Drop` values in ivars
14+
(currently `rc::Id<T, O>`, `Box<T>`, `Option<rc::Id<T, O>>` or
15+
`Option<Box<T>>`).
1316

1417
### Removed
1518
* **BREAKING**: `MaybeUninit` no longer implements `IvarType` directly; use

objc2/examples/delegate.rs

+22-15
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
2-
use objc2::declare::Ivar;
3-
use objc2::foundation::NSObject;
2+
use objc2::declare::{Ivar, IvarDrop};
3+
use objc2::foundation::{NSCopying, NSObject, NSString};
44
use objc2::rc::{Id, Shared};
55
use objc2::runtime::Object;
6-
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ClassType};
6+
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ns_string, ClassType};
77

88
#[cfg(all(feature = "apple", target_os = "macos"))]
99
#[link(name = "AppKit", kind = "framework")]
@@ -23,6 +23,10 @@ declare_class!(
2323
struct CustomAppDelegate {
2424
pub ivar: u8,
2525
another_ivar: bool,
26+
box_ivar: IvarDrop<Box<i32>>,
27+
maybe_box_ivar: IvarDrop<Option<Box<i32>>>,
28+
id_ivar: IvarDrop<Id<NSString, Shared>>,
29+
maybe_id_ivar: IvarDrop<Option<Id<NSString, Shared>>>,
2630
}
2731

2832
unsafe impl ClassType for CustomAppDelegate {
@@ -34,18 +38,17 @@ declare_class!(
3438
#[sel(initWith:another:)]
3539
fn init_with(self: &mut Self, ivar: u8, another_ivar: bool) -> Option<&mut Self> {
3640
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
41+
42+
// TODO: `ns_string` can't be used inside closures; investigate!
43+
let s = ns_string!("def");
44+
3745
this.map(|this| {
3846
Ivar::write(&mut this.ivar, ivar);
39-
Ivar::write(&mut this.another_ivar, another_ivar);
40-
// Note that we could have done this with just:
41-
// *this.ivar = ivar;
42-
// *this.another_ivar = another_ivar;
43-
//
44-
// Since these two ivar types (`u8` and `bool`) are safe to
45-
// initialize from all zeroes; but for this example, we chose
46-
// to be explicit.
47-
48-
// SAFETY: All the instance variables have been initialized
47+
*this.another_ivar = another_ivar;
48+
*this.maybe_box_ivar = None;
49+
*this.maybe_id_ivar = Some(s.copy());
50+
Ivar::write(&mut this.box_ivar, Box::new(2));
51+
Ivar::write(&mut this.id_ivar, NSString::from_str("abc"));
4952
this
5053
})
5154
}
@@ -97,8 +100,12 @@ impl CustomAppDelegate {
97100
fn main() {
98101
let delegate = CustomAppDelegate::new(42, true);
99102

100-
println!("{}", delegate.ivar);
101-
println!("{}", delegate.another_ivar);
103+
println!("{:?}", delegate.ivar);
104+
println!("{:?}", delegate.another_ivar);
105+
println!("{:?}", delegate.box_ivar);
106+
println!("{:?}", delegate.maybe_box_ivar);
107+
println!("{:?}", delegate.id_ivar);
108+
println!("{:?}", delegate.maybe_id_ivar);
102109
}
103110

104111
#[cfg(not(all(feature = "apple", target_os = "macos")))]

objc2/src/__macro_helpers.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub use core::mem::size_of;
1313
pub use core::ops::{Deref, DerefMut};
1414
pub use core::option::Option::{self, None, Some};
1515
pub use core::primitive::{bool, str, u8};
16+
pub use core::ptr::drop_in_place;
1617
pub use core::{compile_error, concat, panic, stringify};
1718
// TODO: Use `core::cell::LazyCell`
1819
pub use std::sync::Once;

objc2/src/declare.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
//! ```
113113
114114
mod ivar;
115+
mod ivar_drop;
115116
mod ivar_forwarding_impls;
116117

117118
use alloc::format;
@@ -122,13 +123,14 @@ use core::ptr;
122123
use core::ptr::NonNull;
123124
use std::ffi::CString;
124125

125-
use crate::encode::{Encode, EncodeArguments, EncodeConvert, Encoding, RefEncode};
126+
use crate::encode::{Encode, EncodeArguments, Encoding, RefEncode};
126127
use crate::ffi;
127128
use crate::runtime::{Bool, Class, Imp, Object, Protocol, Sel};
128129
use crate::sel;
129130
use crate::Message;
130131

131-
pub use ivar::{Ivar, IvarType};
132+
pub use ivar::{InnerIvarType, Ivar, IvarType};
133+
pub use ivar_drop::IvarDrop;
132134

133135
pub(crate) mod private {
134136
pub trait Sealed {}
@@ -439,7 +441,12 @@ impl ClassBuilder {
439441
/// Same as [`ClassBuilder::add_ivar`].
440442
pub fn add_static_ivar<T: IvarType>(&mut self) {
441443
// SAFETY: The encoding is correct
442-
unsafe { self.add_ivar_inner::<T::Type>(T::NAME, &T::Type::__ENCODING) }
444+
unsafe {
445+
self.add_ivar_inner::<<T::Type as InnerIvarType>::__Inner>(
446+
T::NAME,
447+
&T::Type::__ENCODING,
448+
)
449+
}
443450
}
444451

445452
/// Adds the given protocol to self.

objc2/src/declare/ivar.rs

+145-24
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,85 @@ use core::fmt;
22
use core::marker::PhantomData;
33
use core::mem::MaybeUninit;
44
use core::ops::{Deref, DerefMut};
5-
use core::ptr::NonNull;
5+
use core::ptr::{self, NonNull};
66

7-
use crate::encode::EncodeConvert;
7+
use crate::encode::{EncodeConvert, Encoding};
88
use crate::runtime::{ivar_offset, Object};
99

10+
pub(crate) mod private {
11+
pub trait Sealed {}
12+
}
13+
14+
/// Types that may be used in ivars.
15+
///
16+
/// This may be either:
17+
/// - [`bool`].
18+
/// - [`IvarDrop<T>`][super::IvarDrop].
19+
/// - Something that implements [`Encode`][crate::Encode].
20+
///
21+
/// This is a sealed trait, and should not need to be implemented. Open an
22+
/// issue if you know a use-case where this restrition should be lifted!
23+
///
24+
///
25+
/// # Safety
26+
///
27+
/// You cannot rely on any safety guarantees from this.
28+
pub unsafe trait InnerIvarType: private::Sealed {
29+
#[doc(hidden)]
30+
const __ENCODING: Encoding;
31+
32+
// SAFETY: It must be safe to transmute from `__Inner` to `Output`.
33+
#[doc(hidden)]
34+
type __Inner;
35+
36+
/// The type that an `Ivar` containing this will dereference to.
37+
///
38+
/// E.g. `Ivar<IvarDrop<Box<u8>>>` will deref to `Box<u8>`.
39+
type Output;
40+
41+
// SAFETY: The __Inner type must be safe to drop even if zero-initialized.
42+
#[doc(hidden)]
43+
const __MAY_DROP: bool;
44+
45+
#[doc(hidden)]
46+
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output;
47+
48+
#[doc(hidden)]
49+
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output;
50+
51+
#[doc(hidden)]
52+
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output>;
53+
}
54+
55+
impl<T: EncodeConvert> private::Sealed for T {}
56+
unsafe impl<T: EncodeConvert> InnerIvarType for T {
57+
const __ENCODING: Encoding = <Self as EncodeConvert>::__ENCODING;
58+
type __Inner = Self;
59+
type Output = Self;
60+
// Note: We explicitly tell `Ivar` that it shouldn't do anything to drop,
61+
// since if the object was deallocated before an `init` method was called,
62+
// the ivar would not have been initialized properly!
63+
//
64+
// For example in the case of `NonNull<u8>`, it would be zero-initialized
65+
// which is an invalid state for that.
66+
const __MAY_DROP: bool = false;
67+
68+
#[inline]
69+
unsafe fn __to_ref(inner: &Self::__Inner) -> &Self::Output {
70+
inner
71+
}
72+
73+
#[inline]
74+
unsafe fn __to_mut(inner: &mut Self::__Inner) -> &mut Self::Output {
75+
inner
76+
}
77+
78+
#[inline]
79+
fn __to_ptr(inner: NonNull<Self::__Inner>) -> NonNull<Self::Output> {
80+
inner
81+
}
82+
}
83+
1084
/// Helper trait for defining instance variables.
1185
///
1286
/// This should be implemented for an empty marker type, which can then be
@@ -39,7 +113,7 @@ use crate::runtime::{ivar_offset, Object};
39113
/// ```
40114
pub unsafe trait IvarType {
41115
/// The type of the instance variable.
42-
type Type: EncodeConvert;
116+
type Type: InnerIvarType;
43117
/// The name of the instance variable.
44118
const NAME: &'static str;
45119

@@ -132,7 +206,18 @@ pub struct Ivar<T: IvarType> {
132206
/// Make this type allowed in `repr(C)`
133207
inner: [u8; 0],
134208
/// For proper variance and auto traits
135-
item: PhantomData<T::Type>,
209+
item: PhantomData<<T::Type as InnerIvarType>::Output>,
210+
}
211+
212+
impl<T: IvarType> Drop for Ivar<T> {
213+
#[inline]
214+
fn drop(&mut self) {
215+
if <T::Type as InnerIvarType>::__MAY_DROP {
216+
// SAFETY: We drop the inner type, which is guaranteed by
217+
// `__MAY_DROP` to always be safe to drop.
218+
unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) }
219+
}
220+
}
136221
}
137222

138223
impl<T: IvarType> Ivar<T> {
@@ -143,8 +228,12 @@ impl<T: IvarType> Ivar<T> {
143228
///
144229
/// This is similar to [`MaybeUninit::as_ptr`], see that for usage
145230
/// instructions.
146-
pub fn as_ptr(this: &Self) -> *const T::Type {
147-
let ptr: NonNull<Object> = NonNull::from(this).cast();
231+
pub fn as_ptr(this: &Self) -> *const <T::Type as InnerIvarType>::Output {
232+
T::Type::__to_ptr(this.as_inner_ptr()).as_ptr()
233+
}
234+
235+
fn as_inner_ptr(&self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
236+
let ptr: NonNull<Object> = NonNull::from(self).cast();
148237

149238
// SAFETY: The user ensures that this is placed in a struct that can
150239
// be reinterpreted as an `Object`. Since `Ivar` can never be
@@ -159,9 +248,7 @@ impl<T: IvarType> Ivar<T> {
159248
// so that is fine.
160249
let offset = unsafe { T::__offset(ptr) };
161250
// SAFETY: The offset is valid
162-
let ptr = unsafe { Object::ivar_at_offset::<T::Type>(ptr, offset) };
163-
164-
ptr.as_ptr()
251+
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
165252
}
166253

167254
/// Get a mutable pointer to the instance variable.
@@ -174,16 +261,17 @@ impl<T: IvarType> Ivar<T> {
174261
///
175262
/// This is similar to [`MaybeUninit::as_mut_ptr`], see that for usage
176263
/// instructions.
177-
fn as_mut_ptr(this: &mut Self) -> *mut T::Type {
178-
let ptr: NonNull<Object> = NonNull::from(this).cast();
264+
pub fn as_mut_ptr(this: &mut Self) -> *mut <T::Type as InnerIvarType>::Output {
265+
T::Type::__to_ptr(this.as_inner_mut_ptr()).as_ptr()
266+
}
267+
268+
fn as_inner_mut_ptr(&mut self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
269+
let ptr: NonNull<Object> = NonNull::from(self).cast();
179270

180-
// SAFETY: Same as `as_ptr`
271+
// SAFETY: Same as `as_inner_ptr`
181272
let offset = unsafe { T::__offset(ptr) };
182273
// SAFETY: The offset is valid
183-
let ptr = unsafe { Object::ivar_at_offset::<T::Type>(ptr, offset) };
184-
185-
// Safe as *mut T because it came from `&mut Self`
186-
ptr.as_ptr()
274+
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
187275
}
188276

189277
/// Sets the value of the instance variable.
@@ -193,15 +281,19 @@ impl<T: IvarType> Ivar<T> {
193281
///
194282
/// This is similar to [`MaybeUninit::write`], see that for usage
195283
/// instructions.
196-
pub fn write(this: &mut Self, val: T::Type) -> &mut T::Type {
197-
let ptr: *mut MaybeUninit<T::Type> = Self::as_mut_ptr(this).cast();
284+
pub fn write(
285+
this: &mut Self,
286+
val: <T::Type as InnerIvarType>::Output,
287+
) -> &mut <T::Type as InnerIvarType>::Output {
288+
let ptr: *mut MaybeUninit<<T::Type as InnerIvarType>::Output> =
289+
Self::as_mut_ptr(this).cast();
198290
let ivar = unsafe { ptr.as_mut().unwrap_unchecked() };
199291
ivar.write(val)
200292
}
201293
}
202294

203295
impl<T: IvarType> Deref for Ivar<T> {
204-
type Target = T::Type;
296+
type Target = <T::Type as InnerIvarType>::Output;
205297

206298
#[inline]
207299
fn deref(&self) -> &Self::Target {
@@ -210,7 +302,7 @@ impl<T: IvarType> Deref for Ivar<T> {
210302
//
211303
// Since all accesses to a particular ivar only goes through one
212304
// `Ivar`, if we have `&Ivar` we know that `&T` is safe.
213-
unsafe { Self::as_ptr(self).as_ref().unwrap_unchecked() }
305+
unsafe { T::Type::__to_ref(self.as_inner_ptr().as_ref()) }
214306
}
215307
}
216308

@@ -241,25 +333,27 @@ impl<T: IvarType> DerefMut for Ivar<T> {
241333
//
242334
// And using `mut` would create aliasing mutable reference to the
243335
// object.
244-
unsafe { Self::as_mut_ptr(self).as_mut().unwrap_unchecked() }
336+
unsafe { T::Type::__to_mut(self.as_inner_mut_ptr().as_mut()) }
245337
}
246338
}
247339

248340
/// Format as a pointer to the instance variable.
249341
impl<T: IvarType> fmt::Pointer for Ivar<T> {
250342
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251-
let ptr: *const T::Type = &**self;
252-
fmt::Pointer::fmt(&ptr, f)
343+
fmt::Pointer::fmt(&Self::as_ptr(self), f)
253344
}
254345
}
255346

256347
#[cfg(test)]
257348
mod tests {
258349
use core::mem;
259350
use core::panic::{RefUnwindSafe, UnwindSafe};
351+
use std::sync::atomic::{AtomicBool, Ordering};
260352

261353
use super::*;
262-
use crate::{msg_send, test_utils, MessageReceiver};
354+
use crate::foundation::NSObject;
355+
use crate::rc::{Id, Owned};
356+
use crate::{declare_class, msg_send, msg_send_id, test_utils, ClassType, MessageReceiver};
263357

264358
struct TestIvar;
265359

@@ -297,4 +391,31 @@ mod tests {
297391
};
298392
assert_eq!(*obj.foo, 42);
299393
}
394+
395+
#[test]
396+
fn ensure_custom_drop_is_possible() {
397+
static HAS_RUN_DEALLOC: AtomicBool = AtomicBool::new(false);
398+
399+
declare_class!(
400+
#[derive(Debug, PartialEq)]
401+
struct CustomDrop {
402+
ivar: u8,
403+
}
404+
405+
unsafe impl ClassType for CustomDrop {
406+
type Super = NSObject;
407+
}
408+
409+
unsafe impl CustomDrop {
410+
#[sel(dealloc)]
411+
fn dealloc(&mut self) {
412+
HAS_RUN_DEALLOC.store(true, Ordering::SeqCst);
413+
}
414+
}
415+
);
416+
417+
let _: Id<CustomDrop, Owned> = unsafe { msg_send_id![CustomDrop::class(), new] };
418+
419+
assert!(HAS_RUN_DEALLOC.load(Ordering::SeqCst));
420+
}
300421
}

0 commit comments

Comments
 (0)