Skip to content

Commit 5ffd4e4

Browse files
committed
Change ivar documentation to match that Object contains UnsafeCell
1 parent c4dbacf commit 5ffd4e4

File tree

6 files changed

+100
-37
lines changed

6 files changed

+100
-37
lines changed

objc2-foundation/examples/class_with_lifetime.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl<'a> MyObject<'a> {
3434
}
3535

3636
fn get(&self) -> Option<&'a u8> {
37-
unsafe { *self.inner.ivar("_number_ptr") }
37+
unsafe { *self.inner.ivar::<Option<&'a u8>>("_number_ptr") }
3838
}
3939

4040
fn write(&mut self, number: u8) {
@@ -57,9 +57,7 @@ impl<'a> MyObject<'a> {
5757
) -> *mut Object {
5858
let this: *mut Object = unsafe { msg_send![super(this, NSObject::class()), init] };
5959
if let Some(this) = unsafe { this.as_mut() } {
60-
unsafe {
61-
this.set_ivar("_number_ptr", ptr);
62-
}
60+
unsafe { this.set_ivar::<*mut u8>("_number_ptr", ptr) };
6361
}
6462
this
6563
}

objc2/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3131
* Added `"unstable-c-unwind"` feature.
3232
* Added unsafe function `Id::cast` for converting between different types of
3333
objects.
34+
* Added `Object::ivar_ptr` to allow direct access to instance variables
35+
through `&Object`.
3436

3537
### Changed
3638
* **BREAKING:** `Sel` is now required to be non-null, which means that you

objc2/examples/introspection.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ fn main() {
2626
println!("NSObject address: {:p}", obj);
2727

2828
// Access an ivar of the object
29-
// TODO: Fix this!
30-
let isa: *const Class = unsafe { *obj.ivar("isa") };
29+
//
30+
// Note: You should not rely on the `isa` ivar being available,
31+
// this is only for demonstration.
32+
let isa = unsafe { *obj.ivar::<*const Class>("isa") };
3133
println!("NSObject isa: {:?}", isa);
3234

3335
#[cfg(feature = "malloc")]

objc2/src/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
//!
2323
//! // Add an ObjC method for getting the number
2424
//! extern "C" fn my_number_get(this: &Object, _cmd: Sel) -> u32 {
25-
//! unsafe { *this.ivar("_number") }
25+
//! unsafe { *this.ivar::<u32>("_number") }
2626
//! }
2727
//! unsafe {
2828
//! decl.add_method(

objc2/src/runtime.rs

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -625,19 +625,45 @@ impl Object {
625625
unsafe { ptr.as_ref().unwrap_unchecked() }
626626
}
627627

628-
/// Returns a shared reference to the ivar with the given name.
628+
/// Returns a pointer to the instance variable / ivar with the given name.
629+
///
630+
/// This is similar to [`UnsafeCell::get`], see that for more information
631+
/// on what is and isn't safe to do.
632+
///
633+
/// Usually you will have defined the instance variable yourself with
634+
/// [`ClassBuilder::add_ivar`], the type of the ivar `T` must match the
635+
/// type used in that.
636+
///
637+
/// Attempting to access or modify private implementation details of a
638+
/// class that you do no control using this is not supported, and may
639+
/// invoke undefined behaviour.
640+
///
641+
/// Library implementors are strongly encouraged to expose a safe
642+
/// interface to the ivar.
643+
///
644+
/// [`ClassBuilder::add_ivar`]: crate::declare::ClassBuilder::add_ivar
645+
///
629646
///
630647
/// # Panics
631648
///
632-
/// Panics if the object has no ivar with the given name, or the type
633-
/// encoding of the ivar differs from the type encoding of `T`.
649+
/// May panic if the object has no ivar with the given name. May also
650+
/// panic if the type encoding of the ivar differs from the type encoding
651+
/// of `T`.
652+
///
653+
/// This should purely seen as help while debugging and is not guaranteed
654+
/// (e.g. it may be disabled when `debug_assertions` are off).
655+
///
634656
///
635657
/// # Safety
636658
///
637-
/// The caller must ensure that the ivar is actually of type `T`.
659+
/// The object must have an instance variable with the given name, and it
660+
/// must be of type `T`. Any invariants that the object have assumed about
661+
/// the value of the instance variable must not be violated.
638662
///
639-
/// Library implementors should expose a safe interface to the ivar.
640-
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
663+
/// No thread syncronization is done on accesses to the variable, so you
664+
/// must ensure that any access to the returned pointer do not cause data
665+
/// races, and that Rust's mutability rules are not otherwise violated.
666+
pub unsafe fn ivar_ptr<T: Encode>(&self, name: &str) -> *mut T {
641667
let offset = ivar_offset::<T>(self.class(), name);
642668
let ptr: *const Self = self;
643669

@@ -646,32 +672,56 @@ impl Object {
646672
let ptr = unsafe { ptr.offset(offset) };
647673
let ptr: *const T = ptr.cast();
648674

649-
unsafe { ptr.as_ref().unwrap_unchecked() }
675+
// Safe as *mut T because `self` is `UnsafeCell`
676+
ptr as *mut T
677+
}
678+
679+
/// Returns a reference to the instance variable with the given name.
680+
///
681+
/// See [`Object::ivar_ptr`] for more information, including on when this
682+
/// panics.
683+
///
684+
///
685+
/// # Safety
686+
///
687+
/// The object must have an instance variable with the given name, and it
688+
/// must be of type `T`.
689+
///
690+
/// No thread syncronization is done, so you must ensure that no other
691+
/// thread is concurrently mutating the variable. This requirement can be
692+
/// considered upheld if all mutation happens through [`Object::ivar_mut`]
693+
/// (since that takes `&mut self`).
694+
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
695+
// SAFETY: Upheld by caller.
696+
unsafe { self.ivar_ptr::<T>(name).as_ref().unwrap_unchecked() }
650697
}
651698

652-
/// Use [`ivar`][`Self::ivar`] instead.
699+
/// Use [`Object::ivar`] instead.
700+
///
653701
///
654702
/// # Safety
655703
///
656-
/// Same as [`ivar`][`Self::ivar`].
704+
/// See [`Object::ivar`].
657705
#[deprecated = "Use `Object::ivar` instead."]
658706
pub unsafe fn get_ivar<T: Encode>(&self, name: &str) -> &T {
659707
// SAFETY: Upheld by caller
660-
unsafe { self.ivar(name) }
708+
unsafe { self.ivar::<T>(name) }
661709
}
662710

663711
/// Returns a mutable reference to the ivar with the given name.
664712
///
665-
/// # Panics
713+
/// See [`Object::ivar_ptr`] for more information, including on when this
714+
/// panics.
666715
///
667-
/// Panics if the object has no ivar with the given name, or the type
668-
/// encoding of the ivar differs from the type encoding of `T`.
669716
///
670717
/// # Safety
671718
///
672-
/// The caller must ensure that the ivar is actually of type `T`.
719+
/// The object must have an instance variable with the given name, and it
720+
/// must be of type `T`.
673721
///
674-
/// Library implementors should expose a safe interface to the ivar.
722+
/// This access happens through `&mut self`, which means we know it to be
723+
/// the only reference, hence you do not need to do any work to ensure
724+
/// that data races do not happen.
675725
pub unsafe fn ivar_mut<T: Encode>(&mut self, name: &str) -> &mut T {
676726
let offset = ivar_offset::<T>(self.class(), name);
677727
let ptr: *mut Self = self;
@@ -684,29 +734,27 @@ impl Object {
684734
unsafe { ptr.as_mut().unwrap_unchecked() }
685735
}
686736

687-
/// Use [`ivar_mut`](`Self::ivar_mut`) instead.
737+
/// Use [`Object::ivar_mut`] instead.
738+
///
688739
///
689740
/// # Safety
690741
///
691-
/// Same as [`ivar_mut`][`Self::ivar_mut`].
742+
/// Same as [`Object::ivar_mut`].
692743
#[deprecated = "Use `Object::ivar_mut` instead."]
693744
pub unsafe fn get_mut_ivar<T: Encode>(&mut self, name: &str) -> &mut T {
694745
// SAFETY: Upheld by caller
695-
unsafe { self.ivar_mut(name) }
746+
unsafe { self.ivar_mut::<T>(name) }
696747
}
697748

698749
/// Sets the value of the ivar with the given name.
699750
///
700-
/// # Panics
751+
/// This is just a helpful shorthand for [`Object::ivar_mut`], see that
752+
/// for more information.
701753
///
702-
/// Panics if the object has no ivar with the given name, or the type
703-
/// encoding of the ivar differs from the type encoding of `T`.
704754
///
705755
/// # Safety
706756
///
707-
/// The caller must ensure that the ivar is actually of type `T`.
708-
///
709-
/// Library implementors should expose a safe interface to the ivar.
757+
/// Same as [`Object::ivar_mut`].
710758
pub unsafe fn set_ivar<T: Encode>(&mut self, name: &str, value: T) {
711759
// SAFETY: Invariants upheld by caller
712760
unsafe { *self.ivar_mut::<T>(name) = value };
@@ -858,13 +906,28 @@ mod tests {
858906
fn test_object() {
859907
let mut obj = test_utils::custom_object();
860908
assert_eq!(obj.class(), test_utils::custom_class());
861-
let result: u32 = unsafe {
862-
obj.set_ivar("_foo", 4u32);
863-
*obj.ivar("_foo")
909+
910+
let result = unsafe {
911+
obj.set_ivar::<u32>("_foo", 4);
912+
*obj.ivar::<u32>("_foo")
864913
};
865914
assert_eq!(result, 4);
866915
}
867916

917+
#[test]
918+
#[should_panic = "Ivar unknown not found on class CustomObject"]
919+
fn test_object_ivar_unknown() {
920+
let obj = test_utils::custom_object();
921+
let _ = unsafe { *obj.ivar::<u32>("unknown") };
922+
}
923+
924+
#[test]
925+
#[should_panic = "assertion failed: T::ENCODING.equivalent_to_str(ivar.type_encoding())"]
926+
fn test_object_ivar_wrong_type() {
927+
let obj = test_utils::custom_object();
928+
let _ = unsafe { *obj.ivar::<u8>("_foo") };
929+
}
930+
868931
#[test]
869932
fn test_encode() {
870933
fn assert_enc<T: Encode>(expected: &str) {

objc2/src/test_utils.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ pub(crate) fn custom_class() -> &'static Class {
108108
}
109109

110110
extern "C" fn custom_obj_set_foo(this: &mut Object, _cmd: Sel, foo: u32) {
111-
unsafe {
112-
this.set_ivar::<u32>("_foo", foo);
113-
}
111+
unsafe { this.set_ivar::<u32>("_foo", foo) }
114112
}
115113

116114
extern "C" fn custom_obj_get_foo(this: &Object, _cmd: Sel) -> u32 {

0 commit comments

Comments
 (0)