Different concrete implementations for a generic trait

Following up from here I have decided to wrap std::fs in a trait and different implementations.
Now, my goal is full static dispatching I don’t want to pay performance for testability.
So I created a trait that describes operations with generic types such as Read, however, the implementations would need to use concrete types and the compiler should just replace the concrete types on their place like if the trait was never there.
I can’t seem to make it work, trying to return a std::io::Result from the implementation for a trait method that returns std::io::Result but fails to recognize it.
Here the playground link:

<T: Read> means the caller chooses the type and you have no say in what actual type it is.

You can use an associated type in your trait and return Self::Read instead.

For regular functions that would use impl Read, but this syntax is not allowed in trait definitions.

Associated types can also have trait bounds, so if you want to require the associated type to implement Read, you can declare it as

type File: Read;

Here’s the (slightly cleaned up) full playground example.

I would personally add self parameters to the associated functions on the trait, and store the storage backend in the struct instead of using PhantomData<StorageBackend>. You can then call the trait functions as methods of the storage field. If your concrete storage type has no data, as DefaultStorage in your example, there is no real difference, but I find it more intuitive to work with a concrete instance.

Thanks, thats what I ended up doing and worked!