objc2 icon indicating copy to clipboard operation
objc2 copied to clipboard

Issues creating mutable dictionary

Open pronebird opened this issue 8 months ago • 6 comments

Hi,

I am having issues constructing a mutable dictionary, maybe you could help me figure out what I am doing wrong:

static kIOPrimaryInterface: &str = "IOPrimaryInterface";

let property_dict = unsafe {
    CFMutableDictionary::new(
        kCFAllocatorDefault,
        0,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks,
    )
}.unwrap();

let primary_key = unsafe { CFString::from_static_str(kIOPrimaryInterface) };
let value  = unsafe { kCFBooleanTrue.unwrap() };

unsafe { property_dict.set(primary_key, value) };

Errors with:

34  |     unsafe { property_dict.set(primary_key, value) };
    |                            ^^^              ----- expected `&Opaque`, found `&CFBoolean`

note: expected `&Opaque`, found `CFRetained<CFString>`

34  |     unsafe { property_dict.set(primary_key, value) };
    |                                ^^^^^^^^^^^
    = note: expected reference `&objc2_core_foundation::opaque::Opaque`
                  found struct `CFRetained<CFString>`
    = note: expected reference `&objc2_core_foundation::opaque::Opaque`
               found reference `&CFBoolean`

pronebird avatar Apr 20 '25 09:04 pronebird

Yeah, that's because new is auto-generated and hence non-generic for the time being (I realise that I should've made it generic, but that'd be a breaking change now, so won't do that just yet).

Additionally, there's problems with converting the non-generic variant to/from the generic variant, tracked in https://github.com/madsmtm/objc2/issues/654.

For now, I'd suggest to either use some combination of CFMutableDictionary::empty() + .as_opaque(), or try CFRetained::cast_unchecked::<CFMutableDictionary<CFString, CFBoolean>>(dict).

madsmtm avatar Apr 20 '25 09:04 madsmtm

I am still not sure how to tackle this. So ::empty() is a nice shortcut, thanks for that, but I am still not able to set the value anyway:

let property_dict = CFMutableDictionary::empty();
let primary_key = unsafe { CFString::from_static_str(kIOPrimaryInterface) };
let value = unsafe { kCFBooleanTrue.unwrap() };
unsafe { property_dict.set(&primary_key, value) };

Errors with:

the trait bound `CFRetained<CFString>: Type` is not satisfied

[..]


the trait `Type` is not implemented for `CFRetained<CFString>`

pronebird avatar Apr 20 '25 10:04 pronebird

Rust is dumb here, and doesn't know that you want CFMutableDictionary<CFString, CFBoolean> - you have to specify it explicitly as e.g. CFMutableDictionary::<CFString, CFBoolean>::empty(), otherwise it thinks you want something like CFMutableDictionary<CFRetained<CFString>, CFRetained<CFBoolean>>

madsmtm avatar Apr 20 '25 10:04 madsmtm

Got it. Thanks. That was simple considering that I have an empty dictionary with single key value.

So how would you go about mixed dictionary, for example considering an existing dictionary:

static kIOEthernetInterfaceClass: &CStr = c"IOEthernetInterface";
static kIOPropertyMatchKey: &str = "IOPropertyMatch";

let matching_dict /* : CFRetained<CFMutableDictionary> */ = unsafe { IOServiceMatching(kIOEthernetInterfaceClass.as_ptr()) }.unwrap();
let match_key /* : CFRetained<CFString> */ = unsafe { CFString::from_static_str(kIOPropertyMatchKey) };
    unsafe {
        matching_dict
            .as_opaque()
            .set(&match_key, property_dict.as_opaque()) // property_dict from above
    };

And we're back to the same problem:

expected reference `&objc2_core_foundation::opaque::Opaque`
               found reference `&CFRetained<CFString>`
expected reference `&objc2_core_foundation::opaque::Opaque`
               found reference `&CFMutableDictionary`

I am fairly confused because I have no idea how to construct the Opaque type and it's hidden in documentation.

ps: I have tried to deref the match_key (CFRetained<CFString>) which produces &CFString but then I am faced with incompatible type, i.e expected &Opaque type, but got &CFString. Then also Hash is not implemented for Opaque issue...

pronebird avatar Apr 20 '25 10:04 pronebird

ok so casting the dictionary to the key-value type is somewhat working. The key is to use as_opaque() for value.

let casted_matching_dict: CFRetained<CFMutableDictionary<CFString, CFMutableDictionary>> =
        unsafe { CFRetained::cast_unchecked(matching_dict) };
casted_matching_dict.set(&match_key, property_dict.as_opaque());

Ugh working with dictionaries is such a nightmare in Rust.

pronebird avatar Apr 20 '25 11:04 pronebird

Yeah, it's a pain, I'll keep this issue open and track improvements here.

Something that might help you would be to choose CFType for your value generic, since CFString, CFBoolean and the others all Deref to this. But the workaround you did is also fine.

madsmtm avatar Apr 20 '25 17:04 madsmtm