Skip to content

Commit c0f5a90

Browse files
authored
Merge pull request #252 from madsmtm/ivar-init
Better ivar initialization
2 parents 9ab6bf3 + 1ade9a8 commit c0f5a90

File tree

4 files changed

+85
-65
lines changed

4 files changed

+85
-65
lines changed

objc2/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
77

88
## Unreleased - YYYY-MM-DD
99

10+
### Added
11+
* `Ivar::write`, `Ivar::as_ptr` and `Ivar::as_mut_ptr` for querying/modifying
12+
the instance variable inside `init` methods.
13+
14+
### Removed
15+
* **BREAKING**: `MaybeUninit` no longer implements `IvarType` directly; use
16+
`Ivar::write` instead.
17+
1018
## 0.3.0-beta.2 - 2022-08-28
1119

1220
### Added

objc2/examples/class_with_lifetime.rs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//! support such use-cases. Instead, we'll declare the class manually!
55
#![deny(unsafe_op_in_unsafe_fn)]
66
use std::marker::PhantomData;
7-
use std::mem::MaybeUninit;
87
use std::sync::Once;
98

109
use objc2::declare::{ClassBuilder, Ivar, IvarType};
@@ -46,6 +45,19 @@ unsafe impl RefEncode for MyObject<'_> {
4645
unsafe impl Message for MyObject<'_> {}
4746

4847
impl<'a> MyObject<'a> {
48+
unsafe extern "C" fn init_with_ptr(
49+
&mut self,
50+
_cmd: Sel,
51+
ptr: Option<&'a mut u8>,
52+
) -> Option<&'a mut Self> {
53+
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
54+
this.map(|this| {
55+
// Properly initialize the number reference
56+
Ivar::write(&mut this.number, ptr.expect("got NULL number ptr"));
57+
this
58+
})
59+
}
60+
4961
pub fn new(number: &'a mut u8) -> Id<Self, Owned> {
5062
// SAFETY: The lifetime of the reference is properly bound to the
5163
// returned type
@@ -77,38 +89,10 @@ unsafe impl<'a> ClassType for MyObject<'a> {
7789

7890
builder.add_static_ivar::<NumberIvar<'a>>();
7991

80-
/// Helper struct since we can't access the instance variable
81-
/// from inside MyObject, since it hasn't been initialized yet!
82-
#[repr(C)]
83-
struct PartialInit<'a> {
84-
inner: NSObject,
85-
number: Ivar<MaybeUninit<NumberIvar<'a>>>,
86-
}
87-
unsafe impl RefEncode for PartialInit<'_> {
88-
const ENCODING_REF: Encoding = Encoding::Object;
89-
}
90-
unsafe impl Message for PartialInit<'_> {}
91-
92-
impl<'a> PartialInit<'a> {
93-
unsafe extern "C" fn init_with_ptr(
94-
this: &mut Self,
95-
_cmd: Sel,
96-
ptr: Option<&'a mut u8>,
97-
) -> Option<&'a mut Self> {
98-
let this: Option<&mut Self> =
99-
unsafe { msg_send![super(this, NSObject::class()), init] };
100-
this.map(|this| {
101-
// Properly initialize the number reference
102-
this.number.write(ptr.expect("got NULL number ptr"));
103-
this
104-
})
105-
}
106-
}
107-
10892
unsafe {
10993
builder.add_method(
11094
sel!(initWithPtr:),
111-
PartialInit::init_with_ptr as unsafe extern "C" fn(_, _, _) -> _,
95+
Self::init_with_ptr as unsafe extern "C" fn(_, _, _) -> _,
11296
);
11397
}
11498

objc2/examples/delegate.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
2+
use objc2::declare::Ivar;
23
use objc2::foundation::NSObject;
34
use objc2::rc::{Id, Shared};
45
use objc2::runtime::Object;
@@ -31,14 +32,22 @@ declare_class!(
3132

3233
unsafe impl CustomAppDelegate {
3334
#[sel(initWith:another:)]
34-
fn init_with(self: &mut Self, ivar: u8, another_ivar: bool) -> *mut Self {
35-
let this: *mut Self = unsafe { msg_send![super(self), init] };
36-
if let Some(this) = unsafe { this.as_mut() } {
37-
// TODO: Allow initialization through MaybeUninit
38-
*this.ivar = ivar;
39-
*this.another_ivar = another_ivar;
40-
}
41-
this
35+
fn init_with(self: &mut Self, ivar: u8, another_ivar: bool) -> Option<&mut Self> {
36+
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
37+
this.map(|this| {
38+
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
49+
this
50+
})
4251
}
4352

4453
#[sel(myClassMethod)]

objc2/src/declare/ivar.rs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::mem::MaybeUninit;
44
use core::ops::{Deref, DerefMut};
55
use core::ptr::NonNull;
66

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

1010
/// Helper trait for defining instance variables.
@@ -44,14 +44,6 @@ pub unsafe trait IvarType {
4444
const NAME: &'static str;
4545
}
4646

47-
unsafe impl<T: IvarType> IvarType for MaybeUninit<T>
48-
where
49-
T::Type: Encode,
50-
{
51-
type Type = MaybeUninit<T::Type>;
52-
const NAME: &'static str = T::NAME;
53-
}
54-
5547
/// A wrapper type over a custom instance variable.
5648
///
5749
/// This type is not meant to be constructed by itself, it must reside within
@@ -138,7 +130,14 @@ pub struct Ivar<T: IvarType> {
138130
}
139131

140132
impl<T: IvarType> Ivar<T> {
141-
fn get_ref(&self) -> &T::Type {
133+
/// Get a pointer to the instance variable.
134+
///
135+
/// Note that if the ivar has already been initialized, you can simply
136+
/// use the `Deref` implementation to get a reference.
137+
///
138+
/// This is similar to [`MaybeUninit::as_ptr`], see that for usage
139+
/// instructions.
140+
pub fn as_ptr(this: &Self) -> *const T::Type {
142141
// SAFETY: The user ensures that this is placed in a struct that can
143142
// be reinterpreted as an `Object`. Since `Ivar` can never be
144143
// constructed by itself (and is neither Copy nor Clone), we know that
@@ -150,21 +149,27 @@ impl<T: IvarType> Ivar<T> {
150149
// Note: We technically don't have provenance over the object, nor the
151150
// ivar, but the object doesn't have provenance over the ivar either,
152151
// so that is fine.
153-
let ptr = NonNull::from(self).cast::<Object>();
152+
let ptr = NonNull::from(this).cast::<Object>();
154153
let obj = unsafe { ptr.as_ref() };
155154

156155
// SAFETY: User ensures that the `Ivar<T>` is only used when the ivar
157156
// exists and has the correct type
158-
unsafe {
159-
obj.inner_ivar_ptr::<T::Type>(T::NAME)
160-
.as_ref()
161-
.unwrap_unchecked()
162-
}
157+
unsafe { obj.inner_ivar_ptr::<T::Type>(T::NAME) }
163158
}
164159

165-
fn get_mut_ptr(&mut self) -> *mut T::Type {
166-
let ptr = NonNull::from(self).cast::<Object>();
167-
// SAFETY: Same as `get_ref`.
160+
/// Get a mutable pointer to the instance variable.
161+
///
162+
/// This is useful when you want to initialize the ivar inside an `init`
163+
/// method (where it may otherwise not have been safely initialized yet).
164+
///
165+
/// Note that if the ivar has already been initialized, you can simply
166+
/// use the `DerefMut` implementation to get a mutable reference.
167+
///
168+
/// This is similar to [`MaybeUninit::as_mut_ptr`], see that for usage
169+
/// instructions.
170+
fn as_mut_ptr(this: &mut Self) -> *mut T::Type {
171+
let ptr = NonNull::from(this).cast::<Object>();
172+
// SAFETY: Same as `as_ptr`.
168173
//
169174
// Note: We don't use `mut` because the user might have two mutable
170175
// references to different ivars, as such:
@@ -185,6 +190,9 @@ impl<T: IvarType> Ivar<T> {
185190
// And using `mut` would create aliasing mutable reference to the
186191
// object.
187192
//
193+
// Since `Object` is `UnsafeCell`, so mutable access through `&Object`
194+
// is allowed.
195+
//
188196
// TODO: Not entirely sure, it might be safe to just do `as_mut`, but
189197
// this is definitely safe.
190198
let obj = unsafe { ptr.as_ref() };
@@ -194,12 +202,17 @@ impl<T: IvarType> Ivar<T> {
194202
unsafe { obj.inner_ivar_ptr::<T::Type>(T::NAME) }
195203
}
196204

197-
#[inline]
198-
fn get_mut(&mut self) -> &mut T::Type {
199-
// SAFETY: Safe as mutable because there is only one access to a
200-
// particular ivar at a time (since we have `&mut self`). `Object` is
201-
// `UnsafeCell`, so mutable access through `&Object` is allowed.
202-
unsafe { self.get_mut_ptr().as_mut().unwrap_unchecked() }
205+
/// Sets the value of the instance variable.
206+
///
207+
/// This is useful when you want to initialize the ivar inside an `init`
208+
/// method (where it may otherwise not have been safely initialized yet).
209+
///
210+
/// This is similar to [`MaybeUninit::write`], see that for usage
211+
/// instructions.
212+
pub fn write(this: &mut Self, val: T::Type) -> &mut T::Type {
213+
let ptr: *mut MaybeUninit<T::Type> = Self::as_mut_ptr(this).cast();
214+
let ivar = unsafe { ptr.as_mut().unwrap_unchecked() };
215+
ivar.write(val)
203216
}
204217
}
205218

@@ -208,14 +221,20 @@ impl<T: IvarType> Deref for Ivar<T> {
208221

209222
#[inline]
210223
fn deref(&self) -> &Self::Target {
211-
self.get_ref()
224+
// SAFETY: The ivar pointer always points to a valid instance.
225+
//
226+
// Since all accesses to a particular ivar only goes through one
227+
// `Ivar`, if we have `&Ivar` we know that `&T` is safe.
228+
unsafe { Self::as_ptr(self).as_ref().unwrap_unchecked() }
212229
}
213230
}
214231

215232
impl<T: IvarType> DerefMut for Ivar<T> {
216233
#[inline]
217234
fn deref_mut(&mut self) -> &mut Self::Target {
218-
self.get_mut()
235+
// SAFETY: Safe as mutable because there is only one access to a
236+
// particular ivar at a time (since we have `&mut self`).
237+
unsafe { Self::as_mut_ptr(self).as_mut().unwrap_unchecked() }
219238
}
220239
}
221240

0 commit comments

Comments
 (0)