Reputation: 518
struct Disk<T: Read + Seek + Write> {
handle: T,
}
struct Partition<T: Read + Seek + Write> {
disk: Disk<T>,
}
struct File<T: Read + Seek + Write> {
partition: Partition<T>,
}
At the point of struct Partition
, it is no longer interesting what Disk
s trait-bounds are. Through the language design, it is not possible to create a Disk
with a handle what does not have Read + Seek + Write
. Through this example is very simple, types might become extremely complex if they have multiple members with traits.
What I want is:
struct Disk<T: Read + Seek + Write> {
handle: T,
}
type ExDisk = FIXME;
struct Partition {
disk: ExDisk,
}
struct File {
partition: Partition,
}
Upvotes: 3
Views: 2497
Reputation: 300349
How do you abstract generics in nested types?
Rust does abstractions by traits; so use a trait (not a type).
Specifically, Partition
should depend on a generic parameter implementing a trait rather than on Disk<T: ...>
itself.
trait Volume {}
struct Disk<T: Read + Seek + Write> {
handle: T,
}
impl<T: Read + Seek + Write> Volume for Disk<T> {}
struct Partition<V: Volume> {
volume: V,
}
struct File<V: Volume> {
partition: Partition<V>,
}
Alternatively File
could itself depend on an abstract partition.
Note that using this Volume
trait, it is even possible to remove the generic parameter completely; as long as the Volume
trait is object-safe and the object behind does not need to store a local reference:
struct Partition {
volume: Box<Volume>,
}
struct File {
partition: Partition,
}
It adds a tiny bit of over-head (a dynamic allocation + indirect calls), but gives you a single type instead of a family.
Another solution to only reduce verbosity is to introduce a new trait
for specifically this purpose:
trait Volume: Read + Seek + Write {}
impl<T> Volume for T where T: Read + Seek + Write {}
Allows you to thereafter use the Volume
trait as a short-hand for the sum of traits it represents. This does not abstract the disk, but is certainly convenient.
Upvotes: 5