Keep in mind that a trait is not, itself, a type. dyn SomeTrait is, but as you discovered, it's not sized, so it can't be used as a value in a context where the size of the value must be known.
The most common ways to abstract over types implementing a trait are:
Take a reference. Given a type Example which implements the Read trait, a value of type &example or &mut example can be freely converted to type &dyn Read or &mut dyn Read respectively.
This would give
use std::{io::Read, fs::File};
struct Test<'r> {
readable: &'r mut dyn Read,
}
impl<'r> Test<'r> {
pub fn new(readable: &'r mut dyn Read) -> Self {
Self { readable }
}
pub fn read(&mut self, buf: &mut [u8]) {
self.readable.read(buf); // <-- ERROR: cannot borrow data in an `Arc` as mutable
}
}
fn main() {
let mut file = File::open("foo.txt").unwrap();
let test = Test::new(&mut file);
}
Put the value behind some kind of pointer type. This includes Box<dyn Read>, Rc<dyn Read>, Arc<dyn Read>, and more esoteric options. Which one is appropriate depends on your needs around allocation and sharing.
Genericize your code over the trait. A function that can work with anything implementing Read can be defined using a type parameter: fn with_read<R>(reader: R) where R: Read, giving a unique implementation for each type R that's actually used in your program somewhere. For example, let ex: Example = โฆ; with_read(ex); would work. Note that you generally do not abstract struct fields over traits - the definition of the struct itself usually doesn't depend on the traits implemented by the fields, even if any given impl on that type may have such a dependency.
Note that Box<T> is not a trait object and is not doing dynamic dispatch. It is a Box pointing to a concrete type. A trait object using Box would be written Box<dyn Trait>.
You will choose between storing a trait object or a struct of a concrete type whether you want that the same instance of Test is able to own a struct of changing types (all implementing the specified trait) or if a specific instance of Test is tied to a concrete type T. You probably do not need to dynamically change to a inner struct of another type in the same Test instance, but it depends on your specific use case.
But if you were really considering to use Box<T>, then you probably should just store a struct of type T instead, as Box<T> does not give you any benefit. And the user of your struct will still be able to define T: Box<U> if they really need a box. This would still work because if U: Read then Box<U>: Read.
I would say that it is preferable to stick to concrete type T and avoid trait object if possible. You will be more flexible. You would be able to give a references &T/&mut T to let caller use the inner struct as a T (which would not be possible using trait objects).
And you will be able to provide some functions based on which traits is implemented by the inner struct and let the caller be able to give you structs that do not implement all supported traits:
struct<R> Test {
readable: R
}
impl<R> Test<R>
where R: Read,
{
pub fn read(&mut self, buf: &mut [u8]) {
self.readable.read(buf);
}
}
impl<R> Test<R>
where R: Other,
{
pub fn make_something(&mut self) {
self.readable.make_something();
}
}
With a trait object, you will be force to make the bound in Test struct definition, and forcing all the user of Test to provide structs which implements all traits at once:
Yes trait objets can simplify the code by avoiding to have to repeat the type bound everywhere. My point was that if you used Box<T>, it would be easy to refactor to be generic just over T.
Do you need to store the different ways you obtained the response then ?
If you do not plan to treat the data specifically from the kind of source, why not just defining Response as such:
This would require to read the whole file (which could be rather big!) into Vec to build a response
I thought it would be good to be able to just pass a dyn Read (e.g. File).
In this case, the Response::send() would simply "pump through" the contents from the source (e.g. File), in a "chunk by chunk" fashion, without ever having to load the whole thing in memory.
An inference error. The compiler will try to infer a specific type at the end of the day, but if there are absolutely no constraints on it that could be used, then you'll get a compile error.
Ok, I better see your use case now. You want to construct a Response on the server which would then be transferred and abstract the actual source of bytes.
One solution is to consider that you will ultimately only need to write bytes, so a solution is to only consider that body must be Read. You'll avoid to have to specify a weird dummy R type when you are storing an enum variant that is not related to this generic type bound.
You can use std::io::Cursor for types that do not implement Read directly but are AsRef<[u8]>.