objc2_core_foundation/
dictionary.rs

1#[cfg(feature = "alloc")]
2use alloc::vec::Vec;
3use core::{borrow::Borrow, ffi::c_void, hash::Hash};
4
5use crate::{
6    kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionary, CFIndex,
7    CFMutableDictionary, CFRetained, Type,
8};
9
10/// Roughly same as `failed_creating_array`.
11#[cold]
12fn failed_creating_dictionary(len: CFIndex) -> ! {
13    #[cfg(feature = "alloc")]
14    {
15        use alloc::alloc::{handle_alloc_error, Layout};
16        use core::mem::align_of;
17
18        let layout =
19            Layout::array::<(*const (), *const ())>(len as usize).unwrap_or_else(|_| unsafe {
20                Layout::from_size_align_unchecked(0, align_of::<*const ()>())
21            });
22
23        handle_alloc_error(layout)
24    }
25    #[cfg(not(feature = "alloc"))]
26    {
27        panic!("failed allocating CFDictionary holding {len} elements")
28    }
29}
30
31/// These usually doesn't _have_ to be bound by `K: Type`, all that matters is
32/// that they're valid for the dictionary at hand.
33///
34/// But let's keep the bound for now, it might turn out to be necessary.
35#[inline]
36fn to_void<K: ?Sized + Type>(key: &K) -> *const c_void {
37    let key: *const K = key;
38    key.cast()
39}
40
41/// Convenience creation methods.
42impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
43    /// Create a new empty dictionary.
44    #[inline]
45    #[doc(alias = "CFDictionaryCreate")]
46    pub fn empty() -> CFRetained<Self>
47    where
48        K: Type + PartialEq + Hash,
49        V: Type,
50    {
51        Self::from_slices(&[], &[])
52    }
53
54    /// Create a new dictionary from slices of keys and values.
55    ///
56    /// # Panics
57    ///
58    /// Panics if the slices have different lengths.
59    #[inline]
60    #[doc(alias = "CFDictionaryCreate")]
61    pub fn from_slices(keys: &[&K], values: &[&V]) -> CFRetained<Self>
62    where
63        K: Type + PartialEq + Hash,
64        V: Type,
65    {
66        assert_eq!(
67            keys.len(),
68            values.len(),
69            "key and object slices must have the same length",
70        );
71        // Can never happen, allocations in Rust cannot be this large.
72        debug_assert!(keys.len() < CFIndex::MAX as usize);
73        let len = keys.len() as CFIndex;
74
75        // `&T` has the same layout as `*const c_void`, and is non-NULL.
76        let keys = keys.as_ptr().cast::<*const c_void>().cast_mut();
77        let values = values.as_ptr().cast::<*const c_void>().cast_mut();
78
79        // SAFETY: The keys and values are CFTypes (`K: Type` and `V: Type`
80        // bounds), and the dictionary callbacks are thus correct.
81        //
82        // The keys and values are retained internally by the dictionary, so
83        // we do not need to keep them alive ourselves after this.
84        let dictionary = unsafe {
85            CFDictionary::new(
86                None,
87                keys,
88                values,
89                len,
90                &kCFTypeDictionaryKeyCallBacks,
91                &kCFTypeDictionaryValueCallBacks,
92            )
93        }
94        .unwrap_or_else(|| failed_creating_dictionary(len));
95
96        // SAFETY: The dictionary contains no keys and values yet, and thus
97        // it's safe to cast them to `K` and `V` (as the dictionary callbacks
98        // are valid for these types).
99        unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
100    }
101}
102
103/// Convenience creation methods.
104impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
105    /// Create a new empty mutable dictionary.
106    #[inline]
107    #[doc(alias = "CFDictionaryCreateMutable")]
108    pub fn empty() -> CFRetained<Self>
109    where
110        K: Type + PartialEq + Hash,
111        V: Type,
112    {
113        Self::with_capacity(0)
114    }
115
116    /// Create a new mutable dictionary with the given capacity.
117    #[inline]
118    #[doc(alias = "CFDictionaryCreateMutable")]
119    pub fn with_capacity(capacity: usize) -> CFRetained<Self>
120    where
121        K: Type + PartialEq + Hash,
122        V: Type,
123    {
124        let capacity = capacity.try_into().expect("capacity too high");
125
126        // SAFETY: The keys and values are CFTypes (`K: Type` and `V: Type`
127        // bounds), and the dictionary callbacks are thus correct.
128        let dictionary = unsafe {
129            CFMutableDictionary::new(
130                None,
131                capacity,
132                &kCFTypeDictionaryKeyCallBacks,
133                &kCFTypeDictionaryValueCallBacks,
134            )
135        }
136        .unwrap_or_else(|| failed_creating_dictionary(capacity));
137
138        // SAFETY: The dictionary contains no keys and values yet, and thus
139        // it's safe to cast them to `K` and `V` (as the dictionary callbacks
140        // are valid for these types).
141        unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
142    }
143}
144
145/// Direct, unsafe object accessors.
146///
147/// CFDictionary stores its keys and values directly, and you can get
148/// references to those without having to retain them first - but only if the
149/// dictionary isn't mutated while doing so - otherwise, you might end up
150/// accessing a deallocated object.
151impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
152    /// Get a direct reference to one of the dictionary's values.
153    ///
154    /// Consider using the [`get`](Self::get) method instead, unless you're
155    /// seeing performance issues from the retaining.
156    ///
157    /// # Safety
158    ///
159    /// The dictionary must not be mutated while the returned reference is
160    /// live.
161    #[inline]
162    #[doc(alias = "CFDictionaryGetValue")]
163    pub unsafe fn get_unchecked(&self, key: &K) -> Option<&V>
164    where
165        K: Type + Sized,
166        V: Type + Sized,
167    {
168        // SAFETY: The key is valid for this dictionary.
169        //
170        // The values are CoreFoundation types, and thus cannot be NULL, so
171        // no need to use `CFDictionaryGetValueIfPresent`.
172        let value = unsafe { self.as_opaque().value(to_void(key)) };
173        // SAFETY: The dictionary's values are of type `V`.
174        //
175        // Caller ensures that the dictionary isn't mutated for the lifetime
176        // of the reference.
177        unsafe { value.cast::<V>().as_ref() }
178    }
179
180    /// A vector containing direct references to the dictionary's keys and
181    /// values.
182    ///
183    /// Consider using the [`to_vecs`](Self::to_vecs) method instead, unless
184    /// you're seeing performance issues from the retaining.
185    ///
186    /// # Safety
187    ///
188    /// The array must not be mutated while the returned references are alive.
189    #[cfg(feature = "alloc")]
190    pub unsafe fn to_vecs_unchecked(&self) -> (Vec<&K>, Vec<&V>)
191    where
192        K: Type,
193        V: Type,
194    {
195        let len = self.len();
196        let mut keys = Vec::<&K>::with_capacity(len);
197        let mut values = Vec::<&V>::with_capacity(len);
198
199        // `&K`/`&V` has the same layout as `*const c_void`.
200        let keys_ptr = keys.as_mut_ptr().cast::<*const c_void>();
201        let values_ptr = values.as_mut_ptr().cast::<*const c_void>();
202
203        // SAFETY: The arrays are both of the right size.
204        unsafe { self.as_opaque().keys_and_values(keys_ptr, values_ptr) };
205
206        // SAFETY: Just initialized the `Vec`s above.
207        unsafe {
208            keys.set_len(len);
209            values.set_len(len);
210        }
211
212        (keys, values)
213    }
214}
215
216/// Various accessor methods.
217impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
218    /// The amount of elements in the dictionary.
219    #[inline]
220    #[doc(alias = "CFDictionaryGetCount")]
221    pub fn len(&self) -> usize {
222        // Fine to cast here, the count is never negative.
223        self.as_opaque().count() as _
224    }
225
226    /// Whether the dictionary is empty or not.
227    #[inline]
228    pub fn is_empty(&self) -> bool {
229        self.len() == 0
230    }
231
232    /// Retrieve the object at the given index.
233    ///
234    /// Returns `None` if the index was out of bounds.
235    #[doc(alias = "CFDictionaryGetValue")]
236    pub fn get(&self, key: &K) -> Option<CFRetained<V>>
237    where
238        K: Type + Sized + PartialEq + Hash,
239        V: Type + Sized,
240    {
241        // SAFETY: We retain the value right away, so we know the reference is
242        // valid for the duration we use it.
243        unsafe { self.get_unchecked(key) }.map(V::retain)
244    }
245
246    /// Two vectors containing respectively the dictionary's keys and values.
247    #[cfg(feature = "alloc")]
248    #[doc(alias = "CFDictionaryGetKeysAndValues")]
249    pub fn to_vecs(&self) -> (Vec<CFRetained<K>>, Vec<CFRetained<V>>)
250    where
251        K: Type + Sized,
252        V: Type + Sized,
253    {
254        // SAFETY: We retain the elements below, so that we know that the
255        // dictionary isn't mutated while they are alive.
256        let (keys, objects) = unsafe { self.to_vecs_unchecked() };
257        (
258            keys.into_iter().map(K::retain).collect(),
259            objects.into_iter().map(V::retain).collect(),
260        )
261    }
262
263    /// Whether the key is in the dictionary.
264    #[inline]
265    #[doc(alias = "CFDictionaryContainsKey")]
266    pub fn contains_key(&self, key: &K) -> bool
267    where
268        K: Type + Sized + PartialEq + Hash,
269    {
270        // SAFETY: The key is bound by `K: Type`, and thus know to be valid
271        // for the callbacks in the dictionary.
272        unsafe { self.as_opaque().contains_ptr_key(to_void(key)) }
273    }
274
275    /// Whether the value can be found anywhere in the dictionary.
276    #[inline]
277    #[doc(alias = "CFDictionaryContainsValue")]
278    pub fn contains_value(&self, value: &V) -> bool
279    where
280        V: Type + Sized + PartialEq,
281    {
282        // SAFETY: The value is bound by `V: Type`, and thus know to be valid
283        // for the callbacks in the dictionary.
284        unsafe { self.as_opaque().contains_ptr_value(to_void(value)) }
285    }
286}
287
288/// Various mutation methods.
289impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
290    /// Add the key-value pair to the dictionary if no such key already exist.
291    #[inline]
292    #[doc(alias = "CFDictionaryAddValue")]
293    pub fn add(&self, key: &K, value: &V)
294    where
295        K: Type + Sized + PartialEq + Hash,
296        V: Type + Sized,
297    {
298        unsafe {
299            CFMutableDictionary::add_value(Some(self.as_opaque()), to_void(key), to_void(value))
300        }
301    }
302
303    /// Set the value of the key in the dictionary.
304    #[inline]
305    #[doc(alias = "CFDictionarySetValue")]
306    pub fn set(&self, key: &K, value: &V)
307    where
308        K: Type + Sized + PartialEq + Hash,
309        V: Type + Sized,
310    {
311        unsafe {
312            CFMutableDictionary::set_value(Some(self.as_opaque()), to_void(key), to_void(value))
313        }
314    }
315
316    /// Replace the value of the key in the dictionary.
317    #[inline]
318    #[doc(alias = "CFDictionaryReplaceValue")]
319    pub fn replace(&self, key: &K, value: &V)
320    where
321        K: Type + Sized + PartialEq + Hash,
322        V: Type + Sized,
323    {
324        unsafe {
325            CFMutableDictionary::replace_value(Some(self.as_opaque()), to_void(key), to_void(value))
326        }
327    }
328
329    /// Remove the value from the dictionary associated with the key.
330    #[inline]
331    #[doc(alias = "CFDictionaryRemoveValue")]
332    pub fn remove(&self, key: &K)
333    where
334        K: Type + Sized + PartialEq + Hash,
335    {
336        unsafe { CFMutableDictionary::remove_value(Some(self.as_opaque()), to_void(key)) }
337    }
338
339    /// Remove all keys and values from the dictionary.
340    #[inline]
341    #[doc(alias = "CFDictionaryRemoveAllValues")]
342    pub fn clear(&self) {
343        CFMutableDictionary::remove_all_values(Some(self.as_opaque()))
344    }
345}
346
347// Allow easy conversion from `&CFDictionary<T>` to `&CFDictionary`.
348// Requires `Type` bound because of reflexive impl in `cf_type!`.
349impl<K: ?Sized + Type, V: ?Sized + Type> AsRef<CFDictionary> for CFDictionary<K, V> {
350    fn as_ref(&self) -> &CFDictionary {
351        self.as_opaque()
352    }
353}
354impl<K: ?Sized + Type, V: ?Sized + Type> AsRef<CFMutableDictionary> for CFMutableDictionary<K, V> {
355    fn as_ref(&self) -> &CFMutableDictionary {
356        self.as_opaque()
357    }
358}
359
360// `Eq`, `Ord` and `Hash` have the same semantics.
361impl<K: ?Sized + Type, V: ?Sized + Type> Borrow<CFDictionary> for CFDictionary<K, V> {
362    fn borrow(&self) -> &CFDictionary {
363        self.as_opaque()
364    }
365}
366impl<K: ?Sized + Type, V: ?Sized + Type> Borrow<CFMutableDictionary> for CFMutableDictionary<K, V> {
367    fn borrow(&self) -> &CFMutableDictionary {
368        self.as_opaque()
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use crate::CFType;
376
377    #[test]
378    fn empty() {
379        let dict1 = CFDictionary::<CFType, CFType>::empty();
380        let dict2 = CFDictionary::<CFType, CFType>::empty();
381        assert_eq!(dict1, dict2);
382        assert_eq!(dict1.len(), 0);
383        assert_eq!(dict1.get(dict1.as_ref()), None);
384        assert!(!dict1.contains_key(dict1.as_ref()));
385        assert!(!dict1.contains_value(dict1.as_ref()));
386        #[cfg(feature = "alloc")]
387        assert_eq!(dict1.to_vecs(), (alloc::vec![], alloc::vec![]));
388    }
389
390    #[test]
391    #[cfg(feature = "CFString")]
392    fn mutable_dictionary() {
393        use crate::CFString;
394
395        let dict = CFMutableDictionary::<CFString, CFString>::empty();
396        dict.add(&CFString::from_str("a"), &CFString::from_str("b"));
397        dict.add(&CFString::from_str("c"), &CFString::from_str("d"));
398        assert_eq!(dict.len(), 2);
399
400        dict.add(&CFString::from_str("c"), &CFString::from_str("e"));
401        assert_eq!(dict.len(), 2);
402        assert_eq!(
403            dict.get(&CFString::from_str("c")),
404            Some(CFString::from_str("d")),
405        );
406
407        dict.replace(&CFString::from_str("c"), &CFString::from_str("f"));
408        assert_eq!(dict.len(), 2);
409        assert_eq!(
410            dict.get(&CFString::from_str("c")),
411            Some(CFString::from_str("f"))
412        );
413
414        dict.remove(&CFString::from_str("a"));
415        assert_eq!(dict.len(), 1);
416
417        dict.clear();
418        assert_eq!(dict.len(), 0);
419    }
420
421    #[test]
422    #[cfg(feature = "CFString")]
423    fn contains() {
424        use crate::CFString;
425
426        let dict = CFDictionary::from_slices(
427            &[&*CFString::from_str("key")],
428            &[&*CFString::from_str("value")],
429        );
430
431        assert!(dict.contains_key(&CFString::from_str("key")));
432        assert!(dict.get(&CFString::from_str("key")).is_some());
433
434        assert!(!dict.contains_key(&CFString::from_str("invalid key")));
435        assert!(dict.get(&CFString::from_str("invalid key")).is_none());
436
437        assert!(dict.contains_value(&CFString::from_str("value")));
438        assert!(!dict.contains_value(&CFString::from_str("invalid value")));
439    }
440
441    #[test]
442    #[cfg(all(feature = "CFString", feature = "CFNumber"))]
443    fn heterogenous() {
444        use crate::{CFBoolean, CFNumber, CFString, CFType};
445
446        let dict = CFDictionary::<CFType, CFType>::from_slices(
447            &[
448                CFString::from_str("string key").as_ref(),
449                CFNumber::new_isize(4).as_ref(),
450                CFBoolean::new(true).as_ref(),
451            ],
452            &[
453                CFString::from_str("a string value").as_ref(),
454                CFNumber::new_isize(2).as_ref(),
455                CFBoolean::new(false).as_ref(),
456            ],
457        );
458
459        assert_eq!(
460            dict.get(&CFString::from_str("string key")),
461            Some(CFString::from_str("a string value").into())
462        );
463        assert_eq!(
464            dict.get(&CFNumber::new_isize(4)),
465            Some(CFNumber::new_isize(2).into())
466        );
467        assert_eq!(
468            dict.get(CFBoolean::new(true)),
469            Some(CFBoolean::new(false).into())
470        );
471        assert_eq!(dict.get(CFBoolean::new(false)), None);
472    }
473
474    #[test]
475    #[cfg(feature = "CFString")]
476    #[should_panic = "key and object slices must have the same length"]
477    fn from_slices_not_same_length() {
478        use crate::CFString;
479        let _dict =
480            CFDictionary::<CFString, CFString>::from_slices(&[&*CFString::from_str("key")], &[]);
481    }
482}