I've done things like this:
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Poscar<
Comment = String,
Coord = Coords,
Elements = Vec<Element>,
> {
pub comment: Comment,
pub coords: Coord,
pub elements: Elements,
}
Of course, when reading a file, you can only produce owned data:
// functions only defined on the owned type
impl Poscar {
/// Reads a POSCAR from an open file.
pub fn from_buf_reader(f: impl BufRead) -> FailResult<Self> { ... }
}
But when writing a file, you're free to use borrowed stuff:
// functions defined on owned and reference data
impl<Comment, Coord, Elements> Poscar<Comment, Coord, Elements>
where
Comment: AsRef<str>,
Coord: Borrow<Coords>,
Elements: AsRef<[Element]>,
{
/// Writes a POSCAR to an open file.
pub fn to_writer(&self, mut w: impl Write) -> FailResult<()> { ... }
}
I chose to set owned types as the defaults because I find it's common to want to write wrappers around functions that read the file (so I have multiple functions around my codebase that return Poscar
), but I almost never need to annotate the type of things that will be written. (because I usually construct them immediately before the write, and that doesn't require specifying type arguments):
// types are: title: &str, coords: &Coords, elements: &[Element]
Poscar { comment: title, coords, elements }.to_writer(file)?;