#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::{borrow::Borrow, ffi::c_void, hash::Hash}; use crate::{ kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionary, CFIndex, CFMutableDictionary, CFRetained, Type, }; /// Roughly same as `failed_creating_array`. #[cold] fn failed_creating_dictionary(len: CFIndex) -> ! { #[cfg(feature = "alloc")] { use alloc::alloc::{handle_alloc_error, Layout}; use core::mem::align_of; let layout = Layout::array::<(*const (), *const ())>(len as usize).unwrap_or_else(|_| unsafe { Layout::from_size_align_unchecked(0, align_of::<*const ()>()) }); handle_alloc_error(layout) } #[cfg(not(feature = "alloc"))] { panic!("failed allocating CFDictionary holding {len} elements") } } /// These usually doesn't _have_ to be bound by `K: Type`, all that matters is /// that they're valid for the dictionary at hand. /// /// But let's keep the bound for now, it might turn out to be necessary. #[inline] fn to_void(key: &K) -> *const c_void { let key: *const K = key; key.cast() } /// Convenience creation methods. impl CFDictionary { /// Create a new empty dictionary. #[inline] #[doc(alias = "CFDictionaryCreate")] pub fn empty() -> CFRetained where K: Type + PartialEq + Hash, V: Type, { Self::from_slices(&[], &[]) } /// Create a new dictionary from slices of keys and values. /// /// # Panics /// /// Panics if the slices have different lengths. #[inline] #[doc(alias = "CFDictionaryCreate")] pub fn from_slices(keys: &[&K], values: &[&V]) -> CFRetained where K: Type + PartialEq + Hash, V: Type, { assert_eq!( keys.len(), values.len(), "key and object slices must have the same length", ); // Can never happen, allocations in Rust cannot be this large. debug_assert!(keys.len() < CFIndex::MAX as usize); let len = keys.len() as CFIndex; // `&T` has the same layout as `*const c_void`, and is non-NULL. let keys = keys.as_ptr().cast::<*const c_void>().cast_mut(); let values = values.as_ptr().cast::<*const c_void>().cast_mut(); // SAFETY: The keys and values are CFTypes (`K: Type` and `V: Type` // bounds), and the dictionary callbacks are thus correct. // // The keys and values are retained internally by the dictionary, so // we do not need to keep them alive ourselves after this. let dictionary = unsafe { CFDictionary::new( None, keys, values, len, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ) } .unwrap_or_else(|| failed_creating_dictionary(len)); // SAFETY: The dictionary contains no keys and values yet, and thus // it's safe to cast them to `K` and `V` (as the dictionary callbacks // are valid for these types). unsafe { CFRetained::cast_unchecked::(dictionary) } } } /// Convenience creation methods. impl CFMutableDictionary { /// Create a new empty mutable dictionary. #[inline] #[doc(alias = "CFDictionaryCreateMutable")] pub fn empty() -> CFRetained where K: Type + PartialEq + Hash, V: Type, { Self::with_capacity(0) } /// Create a new mutable dictionary with the given capacity. #[inline] #[doc(alias = "CFDictionaryCreateMutable")] pub fn with_capacity(capacity: usize) -> CFRetained where K: Type + PartialEq + Hash, V: Type, { let capacity = capacity.try_into().expect("capacity too high"); // SAFETY: The keys and values are CFTypes (`K: Type` and `V: Type` // bounds), and the dictionary callbacks are thus correct. let dictionary = unsafe { CFMutableDictionary::new( None, capacity, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ) } .unwrap_or_else(|| failed_creating_dictionary(capacity)); // SAFETY: The dictionary contains no keys and values yet, and thus // it's safe to cast them to `K` and `V` (as the dictionary callbacks // are valid for these types). unsafe { CFRetained::cast_unchecked::(dictionary) } } } /// Direct, unsafe object accessors. /// /// CFDictionary stores its keys and values directly, and you can get /// references to those without having to retain them first - but only if the /// dictionary isn't mutated while doing so - otherwise, you might end up /// accessing a deallocated object. impl CFDictionary { /// Get a direct reference to one of the dictionary's values. /// /// Consider using the [`get`](Self::get) method instead, unless you're /// seeing performance issues from the retaining. /// /// # Safety /// /// The dictionary must not be mutated while the returned reference is /// live. #[inline] #[doc(alias = "CFDictionaryGetValue")] pub unsafe fn get_unchecked(&self, key: &K) -> Option<&V> where K: Type + Sized, V: Type + Sized, { // SAFETY: The key is valid for this dictionary. // // The values are CoreFoundation types, and thus cannot be NULL, so // no need to use `CFDictionaryGetValueIfPresent`. let value = unsafe { self.as_opaque().value(to_void(key)) }; // SAFETY: The dictionary's values are of type `V`. // // Caller ensures that the dictionary isn't mutated for the lifetime // of the reference. unsafe { value.cast::().as_ref() } } /// A vector containing direct references to the dictionary's keys and /// values. /// /// Consider using the [`to_vecs`](Self::to_vecs) method instead, unless /// you're seeing performance issues from the retaining. /// /// # Safety /// /// The array must not be mutated while the returned references are alive. #[cfg(feature = "alloc")] pub unsafe fn to_vecs_unchecked(&self) -> (Vec<&K>, Vec<&V>) where K: Type, V: Type, { let len = self.len(); let mut keys = Vec::<&K>::with_capacity(len); let mut values = Vec::<&V>::with_capacity(len); // `&K`/`&V` has the same layout as `*const c_void`. let keys_ptr = keys.as_mut_ptr().cast::<*const c_void>(); let values_ptr = values.as_mut_ptr().cast::<*const c_void>(); // SAFETY: The arrays are both of the right size. unsafe { self.as_opaque().keys_and_values(keys_ptr, values_ptr) }; // SAFETY: Just initialized the `Vec`s above. unsafe { keys.set_len(len); values.set_len(len); } (keys, values) } } /// Various accessor methods. impl CFDictionary { /// The amount of elements in the dictionary. #[inline] #[doc(alias = "CFDictionaryGetCount")] pub fn len(&self) -> usize { // Fine to cast here, the count is never negative. self.as_opaque().count() as _ } /// Whether the dictionary is empty or not. #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Retrieve the object at the given index. /// /// Returns `None` if the index was out of bounds. #[doc(alias = "CFDictionaryGetValue")] pub fn get(&self, key: &K) -> Option> where K: Type + Sized + PartialEq + Hash, V: Type + Sized, { // SAFETY: We retain the value right away, so we know the reference is // valid for the duration we use it. unsafe { self.get_unchecked(key) }.map(V::retain) } /// Two vectors containing respectively the dictionary's keys and values. #[cfg(feature = "alloc")] #[doc(alias = "CFDictionaryGetKeysAndValues")] pub fn to_vecs(&self) -> (Vec>, Vec>) where K: Type + Sized, V: Type + Sized, { // SAFETY: We retain the elements below, so that we know that the // dictionary isn't mutated while they are alive. let (keys, objects) = unsafe { self.to_vecs_unchecked() }; ( keys.into_iter().map(K::retain).collect(), objects.into_iter().map(V::retain).collect(), ) } /// Whether the key is in the dictionary. #[inline] #[doc(alias = "CFDictionaryContainsKey")] pub fn contains_key(&self, key: &K) -> bool where K: Type + Sized + PartialEq + Hash, { // SAFETY: The key is bound by `K: Type`, and thus know to be valid // for the callbacks in the dictionary. unsafe { self.as_opaque().contains_ptr_key(to_void(key)) } } /// Whether the value can be found anywhere in the dictionary. #[inline] #[doc(alias = "CFDictionaryContainsValue")] pub fn contains_value(&self, value: &V) -> bool where V: Type + Sized + PartialEq, { // SAFETY: The value is bound by `V: Type`, and thus know to be valid // for the callbacks in the dictionary. unsafe { self.as_opaque().contains_ptr_value(to_void(value)) } } } /// Various mutation methods. impl CFMutableDictionary { /// Add the key-value pair to the dictionary if no such key already exist. #[inline] #[doc(alias = "CFDictionaryAddValue")] pub fn add(&self, key: &K, value: &V) where K: Type + Sized + PartialEq + Hash, V: Type + Sized, { unsafe { CFMutableDictionary::add_value(Some(self.as_opaque()), to_void(key), to_void(value)) } } /// Set the value of the key in the dictionary. #[inline] #[doc(alias = "CFDictionarySetValue")] pub fn set(&self, key: &K, value: &V) where K: Type + Sized + PartialEq + Hash, V: Type + Sized, { unsafe { CFMutableDictionary::set_value(Some(self.as_opaque()), to_void(key), to_void(value)) } } /// Replace the value of the key in the dictionary. #[inline] #[doc(alias = "CFDictionaryReplaceValue")] pub fn replace(&self, key: &K, value: &V) where K: Type + Sized + PartialEq + Hash, V: Type + Sized, { unsafe { CFMutableDictionary::replace_value(Some(self.as_opaque()), to_void(key), to_void(value)) } } /// Remove the value from the dictionary associated with the key. #[inline] #[doc(alias = "CFDictionaryRemoveValue")] pub fn remove(&self, key: &K) where K: Type + Sized + PartialEq + Hash, { unsafe { CFMutableDictionary::remove_value(Some(self.as_opaque()), to_void(key)) } } /// Remove all keys and values from the dictionary. #[inline] #[doc(alias = "CFDictionaryRemoveAllValues")] pub fn clear(&self) { CFMutableDictionary::remove_all_values(Some(self.as_opaque())) } } // Allow easy conversion from `&CFDictionary` to `&CFDictionary`. // Requires `Type` bound because of reflexive impl in `cf_type!`. impl AsRef for CFDictionary { fn as_ref(&self) -> &CFDictionary { self.as_opaque() } } impl AsRef for CFMutableDictionary { fn as_ref(&self) -> &CFMutableDictionary { self.as_opaque() } } // `Eq`, `Ord` and `Hash` have the same semantics. impl Borrow for CFDictionary { fn borrow(&self) -> &CFDictionary { self.as_opaque() } } impl Borrow for CFMutableDictionary { fn borrow(&self) -> &CFMutableDictionary { self.as_opaque() } } #[cfg(test)] mod tests { use super::*; use crate::CFType; #[test] fn empty() { let dict1 = CFDictionary::::empty(); let dict2 = CFDictionary::::empty(); assert_eq!(dict1, dict2); assert_eq!(dict1.len(), 0); assert_eq!(dict1.get(dict1.as_ref()), None); assert!(!dict1.contains_key(dict1.as_ref())); assert!(!dict1.contains_value(dict1.as_ref())); #[cfg(feature = "alloc")] assert_eq!(dict1.to_vecs(), (alloc::vec![], alloc::vec![])); } #[test] #[cfg(feature = "CFString")] fn mutable_dictionary() { use crate::CFString; let dict = CFMutableDictionary::::empty(); dict.add(&CFString::from_str("a"), &CFString::from_str("b")); dict.add(&CFString::from_str("c"), &CFString::from_str("d")); assert_eq!(dict.len(), 2); dict.add(&CFString::from_str("c"), &CFString::from_str("e")); assert_eq!(dict.len(), 2); assert_eq!( dict.get(&CFString::from_str("c")), Some(CFString::from_str("d")), ); dict.replace(&CFString::from_str("c"), &CFString::from_str("f")); assert_eq!(dict.len(), 2); assert_eq!( dict.get(&CFString::from_str("c")), Some(CFString::from_str("f")) ); dict.remove(&CFString::from_str("a")); assert_eq!(dict.len(), 1); dict.clear(); assert_eq!(dict.len(), 0); } #[test] #[cfg(feature = "CFString")] fn contains() { use crate::CFString; let dict = CFDictionary::from_slices( &[&*CFString::from_str("key")], &[&*CFString::from_str("value")], ); assert!(dict.contains_key(&CFString::from_str("key"))); assert!(dict.get(&CFString::from_str("key")).is_some()); assert!(!dict.contains_key(&CFString::from_str("invalid key"))); assert!(dict.get(&CFString::from_str("invalid key")).is_none()); assert!(dict.contains_value(&CFString::from_str("value"))); assert!(!dict.contains_value(&CFString::from_str("invalid value"))); } #[test] #[cfg(all(feature = "CFString", feature = "CFNumber"))] fn heterogenous() { use crate::{CFBoolean, CFNumber, CFString, CFType}; let dict = CFDictionary::::from_slices( &[ CFString::from_str("string key").as_ref(), CFNumber::new_isize(4).as_ref(), CFBoolean::new(true).as_ref(), ], &[ CFString::from_str("a string value").as_ref(), CFNumber::new_isize(2).as_ref(), CFBoolean::new(false).as_ref(), ], ); assert_eq!( dict.get(&CFString::from_str("string key")), Some(CFString::from_str("a string value").into()) ); assert_eq!( dict.get(&CFNumber::new_isize(4)), Some(CFNumber::new_isize(2).into()) ); assert_eq!( dict.get(CFBoolean::new(true)), Some(CFBoolean::new(false).into()) ); assert_eq!(dict.get(CFBoolean::new(false)), None); } #[test] #[cfg(feature = "CFString")] #[should_panic = "key and object slices must have the same length"] fn from_slices_not_same_length() { use crate::CFString; let _dict = CFDictionary::::from_slices(&[&*CFString::from_str("key")], &[]); } }