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.
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:
// `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)
}}};
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.