How to use a file with BufReader and still be able to write to it?

I want to open a file and read its contents as a BufReader using lines() . I also want to be able to search at the end of the file and write some new lines.

Using let mut file allows me to write to a file, but as soon as I provided the file in BufReader , I can no longer write to it, since the main function no longer has file :

 fn main() { let filename = "tt.txt"; // open a tt.txt file in the local directory let file = OpenOptions::new() .read(true) .write(true) .create(true) .open(filename) .unwrap(); // now read the whole file to get the latest state let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap(); let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap(); let reader = BufReader::new(file); let mut latest_date: Option<Date<Local>> = None; let mut latest_datetime: Option<DateTime<Local>> = None; let mut latest_activity: Option<String> = None; for wrapped_line in reader.lines() { let line = wrapped_line.unwrap(); println!("line: {}", line); if date_re.is_match(&line) { let captures = date_re.captures(&line).unwrap(); let year = captures.at(1).unwrap().parse::<i32>().unwrap(); let month = captures.at(2).unwrap().parse::<u32>().unwrap(); let day = captures.at(3).unwrap().parse::<u32>().unwrap(); latest_date = Some(Local.ymd(year, month, day)); latest_datetime = None; latest_activity = None; } if time_activity_re.is_match(&line) && latest_date != None { let captures = time_activity_re.captures(&line).unwrap(); let hour = captures.at(1).unwrap().parse::<u32>().unwrap(); let minute = captures.at(2).unwrap().parse::<u32>().unwrap(); let activity = captures.at(3).unwrap(); latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0)); latest_activity = if activity.len() > 0 { // TODO: if latest_activity already constains a string, clear it and reuse it // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be Some(activity.to_string()) } else { None }; println!("time activity: {} |{}|", latest_datetime.unwrap(), activity); } } // FIXME: I have to open a second file descriptor to the same file, in order to be able to write to it let mut out = OpenOptions::new() .read(true) .write(true) .create(true) .open(filename) .unwrap(); out.seek(End(0)); let now = Local::now(); if latest_date == None || latest_date.unwrap().year() != now.year() || latest_date.unwrap().month() != now.month() || latest_date.unwrap().day() != now.day() { if (latest_date != None) { // not an empy file, as far as tt is concerned out.write_all(b"\n\n"); } out.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes()); out.write_all(b"\n"); } let activity = env::args().skip(1).join(" "); if (activity.len() > 0) { out.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes()); } else { // if there was no latest activity *and* there is no activity, then there no point in writing a second blank line with just a time if latest_activity == None { return; } out.write_all(format!("{}\n", now.format("%H:%M")).as_bytes()); } // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file). } 

How can I use a single file descriptor to read existing lines and add new lines?

(Code from https://github.com/chrisdew/tt/blob/e899f252014391f2e01c3cc9e281cab1ab88936f/src/main.rs )

+5
source share
2 answers

To avoid moving the value, you can use the link and the new area. Here is how you could do it:

 fn main() { let filename = "tt.txt"; // open a tt.txt file in the local directory let mut file = OpenOptions::new() .read(true) .write(true) .create(true) .open(filename) .unwrap(); // now read the whole file to get the latest state let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap(); let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap(); { // BufReader now borrows the value instead of taking ownership. let reader = BufReader::new(&mut file); let mut latest_date: Option<Date<Local>> = None; let mut latest_datetime: Option<DateTime<Local>> = None; let mut latest_activity: Option<String> = None; for wrapped_line in reader.lines() { let line = wrapped_line.unwrap(); println!("line: {}", line); if date_re.is_match(&line) { let captures = date_re.captures(&line).unwrap(); let year = captures.at(1).unwrap().parse::<i32>().unwrap(); let month = captures.at(2).unwrap().parse::<u32>().unwrap(); let day = captures.at(3).unwrap().parse::<u32>().unwrap(); latest_date = Some(Local.ymd(year, month, day)); latest_datetime = None; latest_activity = None; } if time_activity_re.is_match(&line) && latest_date != None { let captures = time_activity_re.captures(&line).unwrap(); let hour = captures.at(1).unwrap().parse::<u32>().unwrap(); let minute = captures.at(2).unwrap().parse::<u32>().unwrap(); let activity = captures.at(3).unwrap(); latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0)); latest_activity = if activity.len() > 0 { // TODO: if latest_activity already constains a string, clear it and reuse it // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be Some(activity.to_string()) } else { None }; println!("time activity: {} |{}|", latest_datetime.unwrap(), activity); } } } // End of the scope, so now file is not borrowed anymore. file.seek(End(0)); let now = Local::now(); if latest_date == None || latest_date.unwrap().year() != now.year() || latest_date.unwrap().month() != now.month() || latest_date.unwrap().day() != now.day() { if (latest_date != None) { // not an empy file, as far as tt is concerned file.write_all(b"\n\n"); } file.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes()); file.write_all(b"\n"); } let activity = env::args().skip(1).join(" "); if (activity.len() > 0) { file.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes()); } else { // if there was no latest activity *and* there is no activity, then there no point in writing a second blank line with just a time if latest_activity == None { return; } file.write_all(format!("{}\n", now.format("%H:%M")).as_bytes()); } // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file). } 
+7
source

You can use BufReader::into_inner to "restore" a file after it is transferred to BufReader . This can be used in conjunction with Read::by_ref to avoid distributing ownership of the BufReader<File> in the first place:

 use std::fs::File; use std::io::{BufRead, BufReader, Read, Write}; fn example(file: File) { let mut reader = BufReader::new(file); for _ in reader.by_ref().lines() {} let mut out = reader.into_inner(); out.write_all(b"new stuff").unwrap(); } 

Here is an antoyo solution with similar reduced code:

 use std::fs::File; use std::io::{BufRead, BufReader, Write}; fn example(mut file: File) { { let reader = BufReader::new(&file); for _ in reader.lines() {} } file.write_all(b"new stuff").unwrap(); } 

When illegal lifetimes (NLLs) are introduced, this can be simplified by removing the extra curly braces.

+1
source

Source: https://habr.com/ru/post/1236451/


All Articles