2
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 Disks 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,
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
eddy
  • 488
  • 5
  • 18

1 Answers1

3

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I'm not sure how this answers the question (although it evidently does as OP accepted it). OP wanted to be able to say `struct Partition {`, but this answer still has a generic parameter for `Partition` and `File`. – Shepmaster Dec 02 '16 at 14:21
  • 1
    @Shepmaster: I read the question as reducing the complexity (and abstracting away the bounds of inner members), not necessarily removing all bounds. Note that if `Volume` is object-safe, it would be possible to have `Partition` contain ` Box` and it would be parameter-less. – Matthieu M. Dec 02 '16 at 14:38
  • I accepted this because it reduces the complexity. Through it is true that using a trait object in a box `Box` would answer the question more correctly. – eddy Dec 02 '16 at 14:56
  • @eddy: Let me add the variation then, and make it complete. Also, in the future, do not feel pressured to accept an answer right away. An accepted answer is like a "nothing to see here, move along" sign to potential answerers, some might still answers but most will move on to greener pastures. Therefore, I encourage you to only accept answers once they really answer your question, and otherwise wait for a better answer... at least for a few days. – Matthieu M. Dec 02 '16 at 15:23