I'm building a stream-based file parser. The file has the format $TYPE$CONTENT\n where TYPE is 4 bytes and content up to \n.
$TYPE is either an attribute of the current record, or indicates that a new record starts. For example:
strtPerson
nameMustermann
surnMax
strtPerson
nameMustermann
surnRenate
My approach was to open the file, get an iterator over the lines, split the lines and then feed the lines to a parser:
use std::io::{self, BufRead, BufReader};
pub trait Record {
    fn add_line(&mut self, type_: String, content: String) -> &mut dyn Record;
}
pub struct File {
    pub current_record: Option<Box<dyn Record>>,
    pub records: Vec<Box<dyn Record>>,
}
impl File {
    pub fn push_last_record(&mut self) {
        if self.current_record.is_some() {
            self.records.push(self.current_record.take().unwrap());
        }
    }
}
impl Record for File {
    fn add_line(&mut self, type_: String, content: String) -> &mut dyn Record {
        match &type_[..] {
            "strt" => {
                self.push_last_record();
                self.current_record = Some(Box::new(File {
                    current_record: None,
                    records: Vec::new(),
                }));
            }
            _ => {
                if let Some(current) = &mut self.current_record {
                    current.add_line(type_, content);
                }
            }
        }
        self
    }
}
fn main() -> io::Result<()> {
    let f =
        "strtPerson\nnameMustermann\nsurnMax\nstrtPerson\nnameMustermann\nsurnRenate".as_bytes();
    let reader = BufReader::new(f);
    let mut f = File {
        current_record: None,
        records: Vec::new(),
    };
    for line in reader.lines() {
        let line = line.unwrap();
        f.add_line(line[..4].to_owned(), line[5..].to_owned());
    }
    println!("there are {} records read", f.records.len());
    f.push_last_record();
    println!("there are now {} records read", f.records.len());
    Ok(())
}
This solution works, but I think it's cumbersome and error prone to require a call to push_last_record and I think there could be a more idiomatic solution to this.
My main issue is how to create the new Record if the line starts with "start" and somehow save a mutable reference to that so I can directly call the add_line function on the latest record.
It boils down to creating a Box, putting it in the vector and save a mutable reference to it in the struct.
Is there a ready-made pattern for this or could anyone give me a hint on how to make this clean?
EDIT:
Thanks to the hint with last_mut(), I rewrote my code. I'm not sure if it's idiomatic or efficient, but I think it's way cleaner than before.
use std::io::{self, BufRead, BufReader};
pub trait Record {
    fn add_line(&mut self, type_: String, content: String) -> &mut dyn Record;
}
pub struct File<'a> {
    pub current_record: Option<&'a mut Box<dyn Record>>,
    pub records: Vec<Box<dyn Record>>,
}
impl Record for File<'_> {
    fn add_line(&mut self, type_: String, content: String) -> &mut dyn Record {
        match &type_[..] {
            "strt" => {
                self.records.push(Box::new(File {
                    current_record: None,
                    records: Vec::new(),
                }));
            }
            _ => {
                if let Some(current) = &mut self.records.last_mut() {
                    current.add_line(type_, content);
                }
            }
        }
        self
    }
}
fn main() -> io::Result<()> {
    let f =
        "strtPerson\nnameMustermann\nsurnMax\nstrtPerson\nnameMustermann\nsurnRenate".as_bytes();
    let reader = BufReader::new(f);
    let mut f = File {
        current_record: None,
        records: Vec::new(),
    };
    for line in reader.lines() {
        let line = line.unwrap();
        f.add_line(line[..4].to_owned(), line[5..].to_owned());
    }
    println!("there are {} records read", f.records.len());
    Ok(())
}
 
     
    