3

Reading Back What You Wrote in Rust

 3 years ago
source link: https://www.morsecodist.io/blog/rust-writer-ownership
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Reading Back What You Wrote in Rust

Published on 2021-06-21

TLDR: An &mut can implement a trait like Write. So if a struct takes ownership of a generic type that implements Write you can provide an &mut instead so the struct doesn't need to take ownership of the data.

This is another very quick Rust tip. I am thoroughly on the Rust hype train these days and I find myself learning really cool little details about Rust that were not immediately obvious to me. This tip may be too basic to be interesting but most of the unparsing examples I saw don't show this kind of usage and it took me an embarassingly long amount time to figure it out.

I was working on a parsing/unparsing library in Rust and I was trying to use the unparser when I came across a problem. Unparsers take ownership of an underlying Write to write the actual data to. This makes total sense. If the unparser required you to borrow the underlying writer you would need to always pass them around together and make sure their lifetimes are compatible; it would be a huge hassle. My problem was that I wanted to read the data I had just written but my unparser owned that data so I couldn't access it anymore.

Take a look at this implementation of an overly simple CSV unparser:

use std::io;
use std::io::Write;

pub struct MyWriter<W: Write> {
    writer: W,
}

impl <W: Write> MyWriter<W> {
    pub fn new(writer: W) -> Self {
        MyWriter { writer }
    }

    pub fn write(&mut self, row: &[&str]) -> io::Result<()> {
        self.writer.write(row.join(", ").as_bytes())?;
        self.writer.write(&[b'\n'])?;
        Ok(())
    }
}

This takes ownership of a Write. Usually you will be writing to something like a file so this works great. We can even return our new MyWriter on it's own:

use std::io;
use std::fs::File;

fn writer_with_headers() -> io::Result<MyWriter<File>> {
    let file = File::create("foo.csv")?;
    let mut writer = MyWriter::new(file);
    writer.write(&["a", "b", "c"])?;
    Ok(writer)
}

But what happens if we want to read the data we just wrote, like for a test?

use std::io;

#[test]
fn test_writing() {
    let v = Vec::new();
    let mut writer = MyWriter::new(v);
    writer.write(&["a", "b", "c"]).unwrap();
    assert_eq!(std::str::from_utf8(&v).unwrap(), "a, b, c\n");
}

We get an error from the compiler:

error[E0382]: borrow of moved value: `v`
  --> src/main.rs:32:36
   |
29 |     let v = Vec::new();
   |         - move occurs because `v` has type `Vec<u8>`, which does not implement the `Copy` trait
30 |     let mut writer = MyWriter::new(v);
   |                                    - value moved here
31 |     writer.write(&["a", "b", "c"]).unwrap();
32 |     assert_eq!(std::str::from_utf8(&v).unwrap(), "a, b, c\n");
   |                                    ^^ value borrowed here after move

We have given ownership of v to writer, we can't use it anymore. I was stumped. How can we test unparsers if it is impossible to read back the data we wrote? I even ended up writing to a temporary file then reading that file back in but that was ugly, and there are times when you need to read back the data at runtime and this impacts performance substantially.

I am sure by now some of you are banging your head against the wall because of how simple the answer really is. What I didn't know is that &mut Vec<u8> also implements Write so we don't need to give the unparser ownership of the Vec<u8> itself to give it ownership of something that implements Write we can give it an &mut Vec<u8> like so:

use std::io;

#[test]
fn test_writing() {
    let mut v = Vec::new();
    {
        // create a new MyWriter and mutably borrow v inside this scope
        let mut writer = MyWriter::new(&mut v);
        writer.write(&["a", "b", "c"]).unwrap();
        // moving out of this scope destroys `writer` and releases our mutable
        // borrow on v so we can read it again on the next line
    }
    assert_eq!(std::str::from_utf8(&v).unwrap(), "a, b, c\n");
}

It turns out library designers really do know what they are doing. Taking ownership of a generic type that implements Write lets you give the unparser ownersip of the underlying Write if you want but it doesn't force you to. This is yet another awesome usage of type bounds with generics. If this is interesting to you you might want to check out my previous post on Rust type bounds with traits.


Recommend

  • 35
    • www.tuicool.com 6 years ago
    • Cache

    Oops, I Wrote a C++ Compiler

    TLDR;I wrote a .NET library that can compile C/C++ code into a byte code that it can also interpret. It is used in my app iCircuit to simulate Arduinos. You can use it yourself with t...

  • 54

    Hey there ? I’m Fabio, a self-taught developer passionate about open source and empowering people. I also like to make my own tools, so naturally I ended up writing a lot of extensions for one of…

  • 33
    • www.tuicool.com 5 years ago
    • Cache

    How I Wrote a Modern C++ Library in Rust

    Since version 56, Firefox has had a new character encoding conversion library called encoding_rs. It is written in Rust and replaced the old C++ character encoding conversion library called uconv that dated from early 199...

  • 1

    Why and how we wrote a compiler in Rust (blog post series 1/X): the contextThis blog post is the first one of a series, it will give you more context to understand why we decided to write our own...

  • 4

    I Wrote 8 Articles on Medium and Came Back a Year Later: This Is How Much I MadeThe articles that made the most and those that made the least — and why.Photo by

  • 3
    • programmingbydoing.com 2 years ago
    • Cache

    Reading What You Wrote

    Reading What You WroteAuthor: Graham Mitchell Filename: ReadingWhatYouWrote.java Reading What You Wrote Again, make a record to store information about a car. It should contain fields for: t...

  • 1

    What was the first program you wrote? When I was in middle school, a friend showed me that you could write programs on a TI-84 calculator. I didn't know about loops, variables, functions, or condition...

  • 5
    • mojosd.medium.com 2 years ago
    • Cache

    Rust Code Reading Club

    ResponsesThere are currently no responses for this story.Be the first to respond.TL;DR: Join us to read rustc source code and learn how things work!If you wa...

  • 1

    Rust 源码阅读俱乐部 | 第一期 说明: 这不是线上沙龙的文字记录,而是我本人会后的学习记录。 最近 Rust 官方发起了 Rust 源码阅读俱乐部 活动,参见 Rust Code R...

  • 4

    Ebooks Made Me Fall Back in Love With ReadingForget the nostalgia of paper and embrace the near-infinite library.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK