I have a function that reads some JSON and builds something out of it (what is not important). The only argument is an impl std::io::Read:
fn create_store_from_read<T>(read: T) -> Store
where
T: std::io::Read,
{
// Not important.
}
Now, I want to call the function from both branches of an if/else with different arguments:
if let Ok(file) = File::open(path) {
create_store_from_read(file)
}
else {
create_store_from_read("[]".as_bytes())
}
Both File and &[u8] implement Read but the compiler yells at me that the argument in the else branch is expected to be a File. I understand that both branches should "return" the same type but I cannot understand why both branches should use the same type in the called function.
I oversimplified the example code, sorry, my fault. You can find a fully working example at Rust Playground and even if I already know what the error was (I mistakenly typed impl for dyn) I don't know why the compiler presents that error. The expected File, found &[u8] really confuses me.
Mm.. you mean that even if the create_store_from_read function always return the same concrete type the fact that it is generic makes the result of the call appears to the compiler as two different "hidden types" in the if/else branches even if T is not used in the return type of the called function?
If I understand this well... the code I wrote (by mistake) would work in Rust 2024 by specifying an empty use, right?
Hmm... on second thought I also think this might actually be a bug. For my understanding captures are about lifetimes and traits but in your case it seems as if the hidden type gets somehow overriden by it.
Correct. This means the function can change to actually using something parameterized by T without breaking downstream.
You can only opt out of capturing lifetimes so far. You can use precise capturing on stable today by the way.
They're about how the opaque type is parameterized, including generic type parameters. It's not a bug (though the inability to opt out of type captures is a significant limitation).
I was curious about RPIT capturing generics so I tried it out by implementing Store for T: Read and changing the function body to use the captured T:
use std::fs::File;
use std::io::Read;
trait Store {}
struct AStore {}
impl Store for AStore {}
impl<T> Store for T where T: Read {}
fn create_store_from_read<T>(read: T) -> Box<impl Store>
where
T: Read,
{
Box::new(read)
}
There is no error. This means you can make the return value dependent on the generic so it has to capture it as @quinedot explained.