Skip to content

Commit 7040f6e

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

File tree

7 files changed

+530
-44
lines changed

7 files changed

+530
-44
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

+33-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,28 @@ 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| {
46+
// Initialize instance variables
47+
48+
// The explicit way to initialize an instance variable:
3849
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
50+
// For some types like `u8`, `bool`, `Option<Box<T>>` and
51+
// `Option<Id<T, O>>` which are safe to zero-initialize, we
52+
// can simply write to the variable as normal:
53+
*this.another_ivar = another_ivar;
54+
*this.maybe_box_ivar = None;
55+
*this.maybe_id_ivar = Some(s.copy());
56+
// While for others like `&u8`, `Box<T>` or `Id<T, O>`, we
57+
// have to initialize it with `Ivar::write`:
58+
Ivar::write(&mut this.box_ivar, Box::new(2));
59+
Ivar::write(&mut this.id_ivar, NSString::from_str("abc"));
60+
61+
// All the instance variables have been initialized; our
62+
// initializer is sound
4963
this
5064
})
5165
}
@@ -97,8 +111,12 @@ impl CustomAppDelegate {
97111
fn main() {
98112
let delegate = CustomAppDelegate::new(42, true);
99113

100-
println!("{}", delegate.ivar);
101-
println!("{}", delegate.another_ivar);
114+
println!("{:?}", delegate.ivar);
115+
println!("{:?}", delegate.another_ivar);
116+
println!("{:?}", delegate.box_ivar);
117+
println!("{:?}", delegate.maybe_box_ivar);
118+
println!("{:?}", delegate.id_ivar);
119+
println!("{:?}", delegate.maybe_id_ivar);
102120
}
103121

104122
#[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

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

@@ -132,7 +200,18 @@ pub struct Ivar<T: IvarType> {
132200
/// Make this type allowed in `repr(C)`
133201
inner: [u8; 0],
134202
/// For proper variance and auto traits
135-
item: PhantomData<T::Type>,
203+
item: PhantomData<<T::Type as InnerIvarType>::Output>,
204+
}
205+
206+
impl<T: IvarType> Drop for Ivar<T> {
207+
#[inline]
208+
fn drop(&mut self) {
209+
if <T::Type as InnerIvarType>::__MAY_DROP {
210+
// SAFETY: We drop the inner type, which is guaranteed by
211+
// `__MAY_DROP` to always be safe to drop.
212+
unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) }
213+
}
214+
}
136215
}
137216

138217
impl<T: IvarType> Ivar<T> {
@@ -143,8 +222,12 @@ impl<T: IvarType> Ivar<T> {
143222
///
144223
/// This is similar to [`MaybeUninit::as_ptr`], see that for usage
145224
/// instructions.
146-
pub fn as_ptr(this: &Self) -> *const T::Type {
147-
let ptr: NonNull<Object> = NonNull::from(this).cast();
225+
pub fn as_ptr(this: &Self) -> *const <T::Type as InnerIvarType>::Output {
226+
T::Type::__to_ptr(this.as_inner_ptr()).as_ptr()
227+
}
228+
229+
fn as_inner_ptr(&self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
230+
let ptr: NonNull<Object> = NonNull::from(self).cast();
148231

149232
// SAFETY: The user ensures that this is placed in a struct that can
150233
// be reinterpreted as an `Object`. Since `Ivar` can never be
@@ -159,9 +242,7 @@ impl<T: IvarType> Ivar<T> {
159242
// so that is fine.
160243
let offset = unsafe { T::__offset(ptr) };
161244
// SAFETY: The offset is valid
162-
let ptr = unsafe { Object::ivar_at_offset::<T::Type>(ptr, offset) };
163-
164-
ptr.as_ptr()
245+
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
165246
}
166247

167248
/// Get a mutable pointer to the instance variable.
@@ -174,16 +255,17 @@ impl<T: IvarType> Ivar<T> {
174255
///
175256
/// This is similar to [`MaybeUninit::as_mut_ptr`], see that for usage
176257
/// instructions.
177-
fn as_mut_ptr(this: &mut Self) -> *mut T::Type {
178-
let ptr: NonNull<Object> = NonNull::from(this).cast();
258+
pub fn as_mut_ptr(this: &mut Self) -> *mut <T::Type as InnerIvarType>::Output {
259+
T::Type::__to_ptr(this.as_inner_mut_ptr()).as_ptr()
260+
}
261+
262+
fn as_inner_mut_ptr(&mut self) -> NonNull<<T::Type as InnerIvarType>::__Inner> {
263+
let ptr: NonNull<Object> = NonNull::from(self).cast();
179264

180-
// SAFETY: Same as `as_ptr`
265+
// SAFETY: Same as `as_inner_ptr`
181266
let offset = unsafe { T::__offset(ptr) };
182267
// 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()
268+
unsafe { Object::ivar_at_offset::<<T::Type as InnerIvarType>::__Inner>(ptr, offset) }
187269
}
188270

189271
/// Sets the value of the instance variable.
@@ -193,15 +275,19 @@ impl<T: IvarType> Ivar<T> {
193275
///
194276
/// This is similar to [`MaybeUninit::write`], see that for usage
195277
/// 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();
278+
pub fn write(
279+
this: &mut Self,
280+
val: <T::Type as InnerIvarType>::Output,
281+
) -> &mut <T::Type as InnerIvarType>::Output {
282+
let ptr: *mut MaybeUninit<<T::Type as InnerIvarType>::Output> =
283+
Self::as_mut_ptr(this).cast();
198284
let ivar = unsafe { ptr.as_mut().unwrap_unchecked() };
199285
ivar.write(val)
200286
}
201287
}
202288

203289
impl<T: IvarType> Deref for Ivar<T> {
204-
type Target = T::Type;
290+
type Target = <T::Type as InnerIvarType>::Output;
205291

206292
#[inline]
207293
fn deref(&self) -> &Self::Target {
@@ -210,7 +296,7 @@ impl<T: IvarType> Deref for Ivar<T> {
210296
//
211297
// Since all accesses to a particular ivar only goes through one
212298
// `Ivar`, if we have `&Ivar` we know that `&T` is safe.
213-
unsafe { Self::as_ptr(self).as_ref().unwrap_unchecked() }
299+
unsafe { T::Type::__to_ref(self.as_inner_ptr().as_ref()) }
214300
}
215301
}
216302

@@ -241,25 +327,27 @@ impl<T: IvarType> DerefMut for Ivar<T> {
241327
//
242328
// And using `mut` would create aliasing mutable reference to the
243329
// object.
244-
unsafe { Self::as_mut_ptr(self).as_mut().unwrap_unchecked() }
330+
unsafe { T::Type::__to_mut(self.as_inner_mut_ptr().as_mut()) }
245331
}
246332
}
247333

248334
/// Format as a pointer to the instance variable.
249335
impl<T: IvarType> fmt::Pointer for Ivar<T> {
250336
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251-
let ptr: *const T::Type = &**self;
252-
fmt::Pointer::fmt(&ptr, f)
337+
fmt::Pointer::fmt(&Self::as_ptr(self), f)
253338
}
254339
}
255340

256341
#[cfg(test)]
257342
mod tests {
258343
use core::mem;
259344
use core::panic::{RefUnwindSafe, UnwindSafe};
345+
use std::sync::atomic::{AtomicBool, Ordering};
260346

261347
use super::*;
262-
use crate::{msg_send, test_utils, MessageReceiver};
348+
use crate::foundation::NSObject;
349+
use crate::rc::{Id, Owned};
350+
use crate::{declare_class, msg_send, msg_send_id, test_utils, ClassType, MessageReceiver};
263351

264352
struct TestIvar;
265353

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

0 commit comments

Comments
 (0)