Skip to content

Commit 5846dd5

Browse files
committed
Make autoreleasepool take the pool as a parameter
The lifetime of the parameter is the lifetime of references in the pool. This allows us to bound lifetimes on autoreleased objects, thereby making them safe to return.
1 parent 69b5047 commit 5846dd5

File tree

4 files changed

+139
-35
lines changed

4 files changed

+139
-35
lines changed

src/rc/autorelease.rs

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,121 @@
1+
use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};
12
use std::os::raw::c_void;
2-
use crate::runtime::{objc_autoreleasePoolPush, objc_autoreleasePoolPop};
33

4-
// we use a struct to ensure that objc_autoreleasePoolPop during unwinding.
5-
struct AutoReleaseHelper {
4+
/// An Objective-C autorelease pool.
5+
///
6+
/// The pool is drained when dropped.
7+
///
8+
/// This is not `Send`, since `objc_autoreleasePoolPop` must be called on the
9+
/// same thread.
10+
///
11+
/// And this is not `Sync`, since you can only autorelease a reference to a
12+
/// pool on the current thread.
13+
///
14+
/// See [the clang documentation][clang-arc] and
15+
/// [this apple article][memory-mgmt] for more information on automatic
16+
/// reference counting.
17+
///
18+
/// [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html
19+
/// [memory-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
20+
pub struct AutoreleasePool {
621
context: *mut c_void,
722
}
823

9-
impl AutoReleaseHelper {
24+
impl AutoreleasePool {
25+
/// Construct a new autoreleasepool.
26+
///
27+
/// Use the [`autoreleasepool`] block for a safe alternative.
28+
///
29+
/// # Safety
30+
///
31+
/// The caller must ensure that when handing out `&'p AutoreleasePool` to
32+
/// functions that this is the innermost pool.
33+
///
34+
/// Additionally, the pools must be dropped in the same order they were
35+
/// created.
36+
#[doc(alias = "objc_autoreleasePoolPush")]
1037
unsafe fn new() -> Self {
11-
AutoReleaseHelper { context: objc_autoreleasePoolPush() }
38+
AutoreleasePool {
39+
context: objc_autoreleasePoolPush(),
40+
}
1241
}
42+
43+
// TODO: Add helper functions to ensure (with debug_assertions) that the
44+
// pool is innermost when its lifetime is tied to a reference.
1345
}
1446

15-
impl Drop for AutoReleaseHelper {
47+
impl Drop for AutoreleasePool {
48+
/// Drains the autoreleasepool.
49+
#[doc(alias = "objc_autoreleasePoolPop")]
1650
fn drop(&mut self) {
1751
unsafe { objc_autoreleasePoolPop(self.context) }
1852
}
1953
}
2054

21-
/**
22-
Execute `f` in the context of a new autorelease pool. The pool is drained
23-
after the execution of `f` completes.
55+
// TODO:
56+
// #![feature(negative_impls)]
57+
// #![feature(auto_traits)]
58+
// /// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool`
59+
// /// through to the closure inside `autoreleasepool`
60+
// pub unsafe auto trait AutoreleaseSafe {}
61+
// // TODO: Unsure how negative impls work exactly
62+
// unsafe impl !AutoreleaseSafe for AutoreleasePool {}
63+
// unsafe impl !AutoreleaseSafe for &AutoreleasePool {}
64+
// unsafe impl !AutoreleaseSafe for &mut AutoreleasePool {}
2465

25-
This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
26-
*/
27-
pub fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
28-
let _context = unsafe { AutoReleaseHelper::new() };
29-
f()
66+
/// Execute `f` in the context of a new autorelease pool. The pool is drained
67+
/// after the execution of `f` completes.
68+
///
69+
/// This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
70+
///
71+
/// The pool is passed as a reference to the enclosing function to give it a
72+
/// lifetime parameter that autoreleased objects can refer to.
73+
///
74+
/// # Examples
75+
///
76+
/// ```rust
77+
/// use objc::{class, msg_send};
78+
/// use objc::rc::{autoreleasepool, AutoreleasePool, Owned};
79+
/// use objc::runtime::Object;
80+
///
81+
/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
82+
/// let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
83+
/// obj.autorelease(pool)
84+
/// }
85+
///
86+
/// autoreleasepool(|pool| {
87+
/// let obj = needs_lifetime_from_pool(pool);
88+
/// // Use `obj`
89+
/// });
90+
///
91+
/// // `obj` is deallocated when the pool ends
92+
/// ```
93+
///
94+
/// ```rust,compile_fail
95+
/// # use objc::{class, msg_send};
96+
/// # use objc::rc::{autoreleasepool, AutoreleasePool, Owned};
97+
/// # use objc::runtime::Object;
98+
/// #
99+
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
100+
/// # let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
101+
/// # obj.autorelease(pool)
102+
/// # }
103+
/// #
104+
/// // Fails to compile because `obj` does not live long enough for us to
105+
/// // safely take it out of the pool.
106+
///
107+
/// let obj = autoreleasepool(|pool| {
108+
/// let obj = needs_lifetime_from_pool(pool);
109+
/// // Use `obj`
110+
/// obj
111+
/// });
112+
/// ```
113+
///
114+
/// TODO: More examples.
115+
pub fn autoreleasepool<T, F>(f: F) -> T
116+
where
117+
for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe,
118+
{
119+
let pool = unsafe { AutoreleasePool::new() };
120+
f(&pool)
30121
}

src/rc/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ assert!(weak.load().is_null());
4040
```
4141
*/
4242

43+
mod autorelease;
4344
mod owned;
4445
mod retained;
4546
mod strong;
4647
mod weak;
47-
mod autorelease;
4848

49-
pub use self::retained::Retained;
49+
pub use self::autorelease::{autoreleasepool, AutoreleasePool};
5050
pub use self::owned::Owned;
51+
pub use self::retained::Retained;
5152
pub use self::strong::StrongPtr;
5253
pub use self::weak::WeakPtr;
53-
pub use self::autorelease::autoreleasepool;
5454

5555
// These tests use NSObject, which isn't present for GNUstep
5656
#[cfg(all(test, any(target_os = "macos", target_os = "ios")))]
@@ -97,9 +97,9 @@ mod tests {
9797
}
9898
let cloned = obj.clone();
9999

100-
autoreleasepool(|| {
101-
obj.autorelease();
102-
assert!(retain_count(*cloned) == 2);
100+
autoreleasepool(|_| {
101+
obj.autorelease();
102+
assert!(retain_count(*cloned) == 2);
103103
});
104104

105105
// make sure that the autoreleased value has been released

src/rc/owned.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use core::mem;
66
use core::ops::{Deref, DerefMut};
77
use core::ptr::{drop_in_place, NonNull};
88

9+
use super::AutoreleasePool;
910
use super::Retained;
10-
use crate::runtime::{self, Object};
1111

1212
/// A smart pointer that strongly references and uniquely owns an Objective-C
1313
/// object.
@@ -106,6 +106,19 @@ impl<T> Owned<T> {
106106
phantom: PhantomData,
107107
}
108108
}
109+
110+
/// Autoreleases the retained pointer, meaning that the object is not
111+
/// immediately released, but will be when the innermost / current
112+
/// autorelease pool is drained.
113+
#[doc(alias = "objc_autorelease")]
114+
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
115+
#[inline]
116+
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T {
117+
let retained: Retained<T> = self.into();
118+
let ptr = retained.autorelease(pool) as *const T as *mut T;
119+
// SAFETY: The pointer was previously `Owned`, so is safe to be mutable
120+
unsafe { &mut *ptr }
121+
}
109122
}
110123

111124
/// `#[may_dangle]` (see [this][dropck_eyepatch]) would not be safe here,

src/rc/retained.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use core::mem;
66
use core::ops::Deref;
77
use core::ptr::NonNull;
88

9+
use super::AutoreleasePool;
910
use super::Owned;
1011
use crate::runtime::{self, Object};
1112

@@ -153,59 +154,58 @@ impl<T> Retained<T> {
153154

154155
/// TODO
155156
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
156-
pub unsafe fn retain_autoreleased_return(obj: &T) -> Self {
157+
pub unsafe fn retain_autoreleased_return(_obj: *const T) -> Self {
157158
todo!()
158159
}
159160

160161
/// Autoreleases the retained pointer, meaning that the object is not
161162
/// immediately released, but will be when the innermost / current
162163
/// autorelease pool is drained.
163-
///
164-
/// A pointer to the object is returned, but it's validity is only until
165-
/// guaranteed until the innermost pool is drained.
166164
#[doc(alias = "objc_autorelease")]
167165
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
168166
#[inline]
169-
// TODO: Get a lifetime relating to the pool, so that we can return a
170-
// reference instead of a pointer.
171-
pub fn autorelease(self) -> NonNull<T> {
167+
pub fn autorelease<'p>(self, _pool: &'p AutoreleasePool) -> &'p T {
172168
let ptr = mem::ManuallyDrop::new(self).ptr;
173169
// SAFETY: The `ptr` is guaranteed to be valid and have at least one
174170
// retain count.
175171
// And because of the ManuallyDrop, we don't call the Drop
176172
// implementation, so the object won't also be released there.
177173
unsafe { runtime::objc_autorelease(ptr.as_ptr() as *mut Object) };
178-
ptr
174+
// SAFETY: The lifetime is bounded by the type function signature
175+
unsafe { &*ptr.as_ptr() }
179176
}
180177

181178
/// TODO
182179
#[doc(alias = "objc_autoreleaseReturnValue")]
183-
pub fn autorelease_return(self) -> *const T {
180+
pub fn autorelease_return<'p>(self, _pool: &'p AutoreleasePool) -> &'p T {
184181
todo!()
185182
}
186183

187184
/// TODO
188185
///
189-
/// Equivalent to `Retained::retain(&obj).autorelease()`, but slightly
186+
/// Equivalent to `Retained::retain(&obj).autorelease(pool)`, but slightly
190187
/// more efficient.
191188
#[doc(alias = "objc_retainAutorelease")]
192-
pub unsafe fn retain_and_autorelease(obj: &T) -> *const T {
189+
pub unsafe fn retain_and_autorelease<'p>(_obj: *const T, _pool: &'p AutoreleasePool) -> &'p T {
193190
todo!()
194191
}
195192

196193
/// TODO
197194
///
198-
/// Equivalent to `Retained::retain(&obj).autorelease_return()`, but
195+
/// Equivalent to `Retained::retain(&obj).autorelease_return(pool)`, but
199196
/// slightly more efficient.
200197
#[doc(alias = "objc_retainAutoreleaseReturnValue")]
201-
pub unsafe fn retain_and_autorelease_return(obj: &T) -> *const T {
198+
pub unsafe fn retain_and_autorelease_return<'p>(
199+
_obj: *const T,
200+
_pool: &'p AutoreleasePool,
201+
) -> &'p T {
202202
todo!()
203203
}
204204

205205
#[cfg(test)] // TODO
206206
#[doc(alias = "retainCount")]
207207
pub fn retain_count(&self) -> usize {
208-
unsafe { msg_send![self.ptr.as_ptr() as *mut Object, retainCount] }
208+
unsafe { msg_send![self.as_ptr() as *mut Object, retainCount] }
209209
}
210210
}
211211

0 commit comments

Comments
 (0)