`macro_rules` - multiple `impl`s satisfying `AsRef<OsStr>`

I'm writing a convenience macro that calls a function which has a few required arguments.

The function's signature is:

fn foo<S1, I, S2>(thing: S1, list: I) -> Result<...> 
    where
        S1: AsRef<OsStr>,
        I: IntoIterator<Item = S2>,
        S2: AsRef<OsStr>
{
...
}

The macro:

#[macro_export]
macro_rules! call_foo{
    ($thing:expr) => {{
        foo($thing, std::iter::empty())
    }};
    ($thing:expr, $list:expr) => {{
        foo($thing, $list)
    }};
}

The error I'm getting is:

error[E0283]: type annotations needed
  --> src/main.rs:6:9
   |
6  |         foo($thing, std::iter::empty())
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
...
25 |     call_foo!("hello");
   |     ------------------ in this macro invocation
   |
   = note: multiple `impl`s satisfying `_: AsRef<OsStr>` found in the `std` crate:
           - impl AsRef<OsStr> for OsStr;
           - impl AsRef<OsStr> for OsString;
           - impl AsRef<OsStr> for Path;
           - impl AsRef<OsStr> for PathBuf;
           - impl AsRef<OsStr> for String;
           - impl AsRef<OsStr> for str;
note: required by a bound in `foo`
  --> src/main.rs:17:9
   |
13 | fn foo<S1, I, S2>(thing: S1, list: I)
   |    --- required by a bound in this function
...
17 |     S2: AsRef<OsStr>,
   |         ^^^^^^^^^^^^ required by this bound in `foo`
   = note: this error originates in the macro `call_foo` (in Nightly builds, run with -Z macro-backtrace for more info)

I'm assuming it's because a &str also implements IntoIterator<Item = AsRef<OsStr>> but I can't for the life of me figure out how to either:

  • disambiguate the types or
  • provide the correct type annotations

Another option is to change the signature of the function, but I'd honestly like to understand how to solve this for my learning.

A working playground example is here

Do the types S1 and S2 actually need to vary independently? If not, then unifying them to a single type parameter would work:

#![allow(unused)]
use std::ffi::OsStr;

#[macro_export]
macro_rules! call_foo{
    ($thing:expr) => {{
        foo($thing, std::iter::empty())
    }};
    ($thing:expr, $list:expr) => {{
        foo($thing, $list)
    }};
}

fn foo<S, I>(thing: S, list: I)
    where
        S: AsRef<OsStr>,
        I: IntoIterator<Item = S>,
{
    let thing = thing.as_ref();
    dbg!(&thing);
}

fn main() {
    call_foo!("hello", ["world"]); // note the subtle change; this may be a usability hazard
    call_foo!("hello");
}

If they do need to vary independently, then you'll need to settle on a type to bind to S2 for the empty case, as the compiler doesn't have enough information to make that decision for you. std::iter::empty::<&str>() would probably work:

#![allow(unused)]
use std::ffi::OsStr;

#[macro_export]
macro_rules! call_foo{
    ($thing:expr) => {{
        foo($thing, std::iter::empty::<&str>())
    }};
    ($thing:expr, $list:expr) => {{
        foo($thing, $list)
    }};
}

fn foo<S1, I, S2>(thing: S1, list: I)
    where
        S1: AsRef<OsStr>,
        I: IntoIterator<Item = S2>,
        S2: AsRef<OsStr>
{
    let thing = thing.as_ref();
    dbg!(&thing);
}

fn main() {
    call_foo!("hello", &["world"]);
    call_foo!("hello");
}
4 Likes

If you can't change the function(or define a new, more specialized, function), you can nudge inference in the macro instead:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e9d77e88339588397cb446287ca805ba

    // `match $thing{thing=>` extends the lifetime of `$thing` to the whole match statement
    ($thing:expr) => {match $thing{thing=>{
        let mut empty = std::iter::empty();
        if false {
            // hack to ignore the borrow checker in dead code
            loop {}

            // telling rust to use `thing`'s type for the iterator items
            [Some(thing), empty.next()];
        }
        foo(thing, empty)
    }}};
2 Likes

The S2 type parameter is unnecessary. The bound you want is:

fn foo<S, I>(thing: S, list: I) -> Result<...> 
    where
        S: AsRef<OsStr>,
        I: IntoIterator,
        I::Item: AsRef<OsStr>,

In general, don't introduce generic parameters for associated types.

1 Like

This won't solve the issue as std::iter::empty()'s item type still needs to be inferred. Removing S2 doesn't really change things because it is unique determined by whatever is the iterator item type, so one can be determined if and only if the other can be determined.

My comment is unrelated to that particular issue. In general, it can fix similar errors.

This did the trick! Thank you!