Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redo reference counting #98

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 105 additions & 14 deletions src/rc/autorelease.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,121 @@
use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};
use std::os::raw::c_void;
use crate::runtime::{objc_autoreleasePoolPush, objc_autoreleasePoolPop};

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

impl AutoReleaseHelper {
impl AutoreleasePool {
/// Construct a new autoreleasepool.
///
/// Use the [`autoreleasepool`] block for a safe alternative.
///
/// # Safety
///
/// The caller must ensure that when handing out `&'p AutoreleasePool` to
/// functions that this is the innermost pool.
///
/// Additionally, the pools must be dropped in the same order they were
/// created.
#[doc(alias = "objc_autoreleasePoolPush")]
unsafe fn new() -> Self {
AutoReleaseHelper { context: objc_autoreleasePoolPush() }
AutoreleasePool {
context: objc_autoreleasePoolPush(),
}
}

// TODO: Add helper functions to ensure (with debug_assertions) that the
// pool is innermost when its lifetime is tied to a reference.
}

impl Drop for AutoReleaseHelper {
impl Drop for AutoreleasePool {
/// Drains the autoreleasepool.
#[doc(alias = "objc_autoreleasePoolPop")]
fn drop(&mut self) {
unsafe { objc_autoreleasePoolPop(self.context) }
}
}

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

This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
*/
pub fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
let _context = unsafe { AutoReleaseHelper::new() };
f()
/// Execute `f` in the context of a new autorelease pool. The pool is drained
/// after the execution of `f` completes.
///
/// This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
///
/// The pool is passed as a reference to the enclosing function to give it a
/// lifetime parameter that autoreleased objects can refer to.
///
/// # Examples
///
/// ```rust
/// use objc::{class, msg_send};
/// use objc::rc::{autoreleasepool, AutoreleasePool, Owned};
/// use objc::runtime::Object;
///
/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
/// obj.autorelease(pool)
/// }
///
/// autoreleasepool(|pool| {
/// let obj = needs_lifetime_from_pool(pool);
/// // Use `obj`
/// });
///
/// // `obj` is deallocated when the pool ends
/// ```
///
/// ```rust,compile_fail
/// # use objc::{class, msg_send};
/// # use objc::rc::{autoreleasepool, AutoreleasePool, Owned};
/// # use objc::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// # let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
/// # obj.autorelease(pool)
/// # }
/// #
/// // Fails to compile because `obj` does not live long enough for us to
/// // safely take it out of the pool.
///
/// let obj = autoreleasepool(|pool| {
/// let obj = needs_lifetime_from_pool(pool);
/// // Use `obj`
/// obj
/// });
/// ```
///
/// TODO: More examples.
pub fn autoreleasepool<T, F>(f: F) -> T
where
for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe,
{
let pool = unsafe { AutoreleasePool::new() };
f(&pool)
}
33 changes: 9 additions & 24 deletions src/rc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ assert!(weak.load().is_null());
```
*/

mod autorelease;
mod owned;
mod retained;
mod strong;
mod weak;
mod autorelease;

pub use self::autorelease::{autoreleasepool, AutoreleasePool};
pub use self::owned::Owned;
pub use self::retained::Retained;
pub use self::strong::StrongPtr;
pub use self::weak::WeakPtr;
pub use self::autorelease::autoreleasepool;

// These tests use NSObject, which isn't present for GNUstep
#[cfg(all(test, any(target_os = "macos", target_os = "ios")))]
Expand All @@ -55,25 +59,6 @@ mod tests {
use super::StrongPtr;
use super::autoreleasepool;

#[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 {
Expand Down Expand Up @@ -112,9 +97,9 @@ mod tests {
}
let cloned = obj.clone();

autoreleasepool(|| {
obj.autorelease();
assert!(retain_count(*cloned) == 2);
autoreleasepool(|_| {
obj.autorelease();
assert!(retain_count(*cloned) == 2);
});

// make sure that the autoreleased value has been released
Expand Down
Loading