How to make a struct with parts that can be borrowed or owned?

I have a type that I can parse from a file (in this case with a nom parser). Since there can potentially be long strings that should be parsed verbatim (like here), I borrow them from the input string by storing an &'a str. However, there are use-cases when I would like to work with this type as a fully-owned type (similar to how sometimes you want a &str and sometimes you need a String).

How should I go about making it possible to use it as an owned type? Is replacing all &'a str fields with Cow<'a, str> the right approach? This doesn't seem 100% clean because then I have to come up with an arbitrary lifetime in the owned case (like 'static).

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>
    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)?;
1 Like

Could you implement a borrowing read with something like this?

fn poscar_from_str<'a>(s: &'a str) -> Poscar<&'a str, Coords, Vec<Element>> { ... }

Ah, yes, I missed that bit in the OP. Yeah, of course, you could do that, though I suppose in this case you cannot just conveniently use &str as a default due to the lifetime. If in the future you have a lot of fields and it gets onerous to write out all the type arguments, you can add a type alias like

type PoscarFromStr<'a> = Poscar<&'a str, Coords, Vec<Element>>;

(Aside: it's pretty rare that I encounter a filetype where zero-copy parsing of strings like that is actually possible, due to things like escape sequences; but, incidentally, Poscar is one such format where it would be possible!)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.