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#[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#[inline]
36fn to_void<K: ?Sized + Type>(key: &K) -> *const c_void {
37 let key: *const K = key;
38 key.cast()
39}
40
41impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
43 #[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 #[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 debug_assert!(keys.len() < CFIndex::MAX as usize);
73 let len = keys.len() as CFIndex;
74
75 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 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 unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
100 }
101}
102
103impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
105 #[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 #[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 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 unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
142 }
143}
144
145impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
152 #[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 let value = unsafe { self.as_opaque().value(to_void(key)) };
173 unsafe { value.cast::<V>().as_ref() }
178 }
179
180 #[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 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 unsafe { self.as_opaque().keys_and_values(keys_ptr, values_ptr) };
205
206 unsafe {
208 keys.set_len(len);
209 values.set_len(len);
210 }
211
212 (keys, values)
213 }
214}
215
216impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
218 #[inline]
220 #[doc(alias = "CFDictionaryGetCount")]
221 pub fn len(&self) -> usize {
222 self.as_opaque().count() as _
224 }
225
226 #[inline]
228 pub fn is_empty(&self) -> bool {
229 self.len() == 0
230 }
231
232 #[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 unsafe { self.get_unchecked(key) }.map(V::retain)
244 }
245
246 #[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 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 #[inline]
265 #[doc(alias = "CFDictionaryContainsKey")]
266 pub fn contains_key(&self, key: &K) -> bool
267 where
268 K: Type + Sized + PartialEq + Hash,
269 {
270 unsafe { self.as_opaque().contains_ptr_key(to_void(key)) }
273 }
274
275 #[inline]
277 #[doc(alias = "CFDictionaryContainsValue")]
278 pub fn contains_value(&self, value: &V) -> bool
279 where
280 V: Type + Sized + PartialEq,
281 {
282 unsafe { self.as_opaque().contains_ptr_value(to_void(value)) }
285 }
286}
287
288impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
290 #[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 #[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 #[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 #[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 #[inline]
341 #[doc(alias = "CFDictionaryRemoveAllValues")]
342 pub fn clear(&self) {
343 CFMutableDictionary::remove_all_values(Some(self.as_opaque()))
344 }
345}
346
347impl<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
360impl<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}