let cached_name = self.cached_name.clone();
let cached_name_inner = cached_name.clone();
let send_r_obj = SendRobj(self.inner.clone());

{% for param in params_to_clone %}
{{ param }}
{% endfor %}

tokio::task::spawn_blocking(move || {
    // Call `into_inner()` on the whole `SendRobj` value so the closure captures the whole
    // `SendRobj` (which is `Send`) rather than just the inner `Robj` field (which is `!Send`)
    // under 2021+ disjoint-capture rules. The method takes `self` by value, forcing the
    // closure to move the whole wrapper.
    let r_obj = send_r_obj.into_inner();
    let maybe_fn = r_obj.dollar("{{ method_name }}");
    let fn_robj = match maybe_fn {
        Ok(v) if !v.is_null() && !v.is_na() => v,
        _ => return Err({{ missing_method_error }}),
    };

{% if empty_args %}
    let result = fn_robj.call(extendr_api::Pairlist::new());
{% else %}
    let args = extendr_api::Pairlist::from_pairs(&[{{ args_pairs }}]);
    let result = fn_robj.call(args);
{% endif %}

    match result {
        Err(_) => {
            Err({{ failed_method_error }})
        }
        Ok(val) => {
            if let Some(s) = val.as_str() {
                Ok(s.to_string())
            } else {
                Err({{ invalid_type_error }})
            }
        }
    }
})
.await
.map_err(|e| {{ spawn_blocking_error }})?
