Work around library with private trait in public trait bound

I'm using the wrapped_mono crate which has a generic struct Method that has a trait bound, but the trait is private, which prevents me from using the type generically. Is there a workaround?

https://github.com/FractalFir/wrapped_mono/blob/b3053fa3e8d78e2f774d987a578077acfbab792f/src/method.rs#L10

Minimal reproducer:

Yes, I can think of a few approaches to start the workaround. Could you give a slightly less minimal shape of the kind of generic API you’re trying to create using that type?

The “minimal reproducer” shows a function f that would produce a value of type Method<Args>, but it isn’t clear if that’s your indended “final” API, or just a helper – if you want to put the Method<Args> into other structures or not… if you want to call that f function itself generically (and whether or not - and if yes how much of - the API of Method you would like to be usable generically, too) things like that :slight_smile:

this is an example of what's called "sealed trait". essentially, the author of the library is intentionally prohibit the trait to be named outside the crate, and the user are expected to only use the implementors provided by the crate.

more details on sealed traits:

Usually sealed traits are not made unnamable, but made with unnamable supertraits merely to prevent downstream implementations, but not prevent downstream uses in trait bounds.

So I wouldn’t say this doesn’t much follow the “standard” sealed-trait pattern; on the other hand, I guess they also wanted the trait items to be unusable, and the standard library does similar things for some API like e.g. str::find [just using an unstable trait instead of public-in-private, but the effect is mostly the same], so perhaps there was no particularly easy better way.

I want to create Method<Args>. This is what I'd like to have:

pub fn method_from_desc_in_class<Args>(desc: &CStr, class: &Class) -> Option<Method<Args>> {
    let desc = desc.as_ptr();
    unsafe {
        let desc = mono_method_desc_new(desc, 0);
        if desc.is_null() {
            return None;
        }
        Some(Method::from_ptr_checked(mono_method_desc_search_in_class(desc, class.get_ptr())))
    }
}

Yes, that much I got. Just wondering e.g. if you then further want to do anything with that Option<Method<Args>> value - in a generic context, too :wink:


Edit: your reply was very useful nonetheless, since you show the API surface you want to use within method_from_desc_in_class, which is also relevant to check if any particular workaround approach actually works.

No, method_from_desc_in_class should only be called with concrete type arguments.

Are mono_method_desc_new and mono_method_desc_search_in_class also going to be defined by you or where do they come from?

They are from mono. I bindgen'd them because the wrapped_mono doesn't expose them.
The signatures are

extern "C" {
    pub fn mono_method_desc_new(
        name: *const ::std::os::raw::c_char,
        include_namespace: mono_bool,
    ) -> *mut MonoMethodDesc;
    pub fn mono_method_desc_search_in_class(
        desc: *mut MonoMethodDesc,
        klass: *mut MonoClass,
    ) -> *mut MonoMethod;
}

C source: https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/debug-helpers.c#L357

I've uploaded the project to GitHub: https://github.com/jendrikw/unclib

This is what I can offer (I haven’t looked at the GitHub link yet, just used the signatures from the previous reply):

use std::ffi::CStr;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct _MonoMethodDesc {
    _unused: [u8; 0],
}
pub type MonoMethodDesc = *mut _MonoMethodDesc;

unsafe extern "C" {
    pub fn mono_method_desc_new(
        name: *const ::std::os::raw::c_char,
        include_namespace: mono_bool,
    ) -> *mut MonoMethodDesc;
    pub fn mono_method_desc_search_in_class(
        desc: *mut MonoMethodDesc,
        klass: *mut MonoClass,
    ) -> *mut MonoMethod;
}

// N.B. This uses doc-hidden API
use wrapped_mono::binds::{MonoClass, MonoMethod, mono_bool};
use wrapped_mono::{Class, InteropClass, InteropSend, Method as WrappedMonoMethod};

pub type Method<Args> = <Args as MethodArgs>::Method;

pub unsafe trait MethodArgs {
    type Method;
    unsafe fn method_from_ptr_checked(met_ptr: *mut MonoMethod) -> Option<Method<Self>>;
}

macro_rules! method_args_impl {
    ($($A:ident)* | $B:tt $($C:tt)*) => {

        unsafe impl<$($A),*> MethodArgs for ($($A,)*)
        where
            $($A: InteropSend + InteropClass,)*
        {
            type Method = WrappedMonoMethod<Self>;

            unsafe fn method_from_ptr_checked(met_ptr: *mut MonoMethod) -> Option<Method<Self>> {
                unsafe { WrappedMonoMethod::from_ptr_checked(met_ptr) }
            }
        }

        method_args_impl!($($A)* $B | $($C)*);
    };
    ($($A:ident)* . |) => {};
}
method_args_impl!(| A B C D E F G H I J K L M N O P .);
pub fn method_from_desc_in_class<Args: MethodArgs>(
    desc: &CStr,
    class: &Class,
) -> Option<Method<Args>> {
    let desc = desc.as_ptr();
    unsafe {
        let desc = mono_method_desc_new(desc, 0);
        if desc.is_null() {
            return None;
        }
        Args::method_from_ptr_checked(mono_method_desc_search_in_class(desc, class.get_ptr()))
    }
}

looking at the definition and implementors of Args, the only workaround I can think of is to duplicate the code for variadic tuples. you can use a macro to reduce some boilerplates, but I think this kind of code duplication is inevitable in this case, without patching wrapped-mono, that is.

something like this:

fn method_from_desc_in_class0<>(desc: &CStr, class: &Class) -> Option<Method<()>>;
fn method_from_desc_in_class1<A1: InteropSend>(desc: &CStr, class: &Class) -> Option<Method<(A1,)>>;
fn method_from_desc_in_class2<A1: InteropSend, A2: InteropSend>(desc: &CStr, class: &Class) -> Option<Method<(A1, A2)>>;
// and so on and so on