Suppose I have two (or more) generic functions with identical signatures. They're only generic because one of the arguments is an AsRef<Path>
. For example
use std::convert::AsRef;
use std::path::Path;
type Result = std::io::Result<()>;
fn operation_1<P: AsRef<Path>>(p: P) -> Result { todo!() }
fn operation_2<P: AsRef<Path>>(p: P) -> Result { todo!() }
fn direct(p: &Path) -> Result { operation_1(p) }
direct
is included just to demonstrate that &Path
does in fact satisfy a : AsRef<Path>
bound, as one would expect. The above compiles (modulo complaints about nothing being public).
To factor repeated code out of some tests, I want to write a wrapper function that takes an &Path
and either operation_1
or operation_2
as an argument, and it calls the function it was given (plus it does some other stuff that's not relevant here). As best I can tell, that wrapper would be written something like
fn wrapper
<P: AsRef<Path>, F: Fn(P) -> Result>
(f: F, p: &Path) -> Result
{ f(p) }
But if you paste this below the code above, it does not compile. You don't even have to call it, the compiler chokes on the body of the wrapper:
error[E0308]: mismatched types
--> file_integrity/src/argh.rs:14:5
|
12 | <P: AsRef<Path>, F: Fn(P) -> Result>
| - this type parameter
13 | (f: F, p: &Path) -> Result
14 | { f(p) }
| - ^ expected type parameter `P`, found `&Path`
| |
| arguments to this function are incorrect
|
= note: expected type parameter `P`
found reference `&std::path::Path`
note: callable defined here
--> file_integrity/src/argh.rs:12:25
|
12 | <P: AsRef<Path>, F: Fn(P) -> Result>
| ^^^^^^^^^^^^^^^
And now I'm stuck. I don't understand why &Path
is considered not to match P
in this context, when P: AsRef<Path>
and passing &Path
directly to a function with a generic argument of type P: AsRef<Path>
is acceptable. Have I written the type signature wrong? (How else could it be written?) Have I tripped over some limit in the type system? Do I need to do something at the call site that wasn't necessary in direct
, and if so, what is it, and why?
Incidentally, I also tried not having wrapper
be generic, and instead manually monomorphizing at the call sites...
fn wrapper(f: fn(&Path) -> Result, p: &Path) -> Result { f(p) }
fn wrapped(p: &Path) -> Result { wrapper(operation_1::<&Path>, p) }
... and that didn't work either:
error[E0308]: mismatched types
--> argh.rs:12:42
|
12 | fn wrapped(p: &Path) -> Result { wrapper(operation_1::<&Path>, p) }
| ------- ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
| |
| arguments to this function are incorrect
|
= note: expected fn pointer `for<'a> fn(&'a std::path::Path) -> std::result::Result<_, _>`
found fn item `fn(&std::path::Path) -> std::result::Result<_, _> {argh::operation_1::<&std::path::Path>}`
= note: when the arguments and return types match, functions can be coerced to function pointers
note: function defined here
--> argh.rs:11:4
|
11 | fn wrapper(f: fn(&Path) -> Result, p: &Path) -> Result { f(p) }
| ^^^^^^^ ----------------------
(What on earth? What does for<'a> fn(&'a Path)
even mean that's different from fn(&Path)
?