Rustコトハジメ

プログラミング言語Rustに関する情報をお届けします。

Cursorの実装からrvalueを学ぶ

ReadのCursorインスタンスの実装を調べている時に、気になったことがあったので共有する。

以下に示す実装は、何でもいいから&[u8]を導けるもの(AsRef<[u8]>)をCursorでラップすればReadのインスタンスになれるという意味だ。実装としては、

www.rustforbeginners.com

で紹介した&[u8]に対しての実装がファットポインタの上書きで実装するのに比べるといくらか穏便で、Cursorの持つposメンバを書き換えている。

impl<T> Read for Cursor<T> where T: AsRef<[u8]> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let n = Read::read(&mut self.fill_buf()?, buf)?;
        self.pos += n as u64;
        Ok(n)
    }

この中の、

let n = Read::read(&mut self.fill_buf()?, buf)?;

が気になったところだ。&mutをするにはlet mutしてないといけないと記憶していたが、fill_bufをいきなり&mutしている。自然ではあるが、知っているルールに反する。

fill_bufの型は、

impl<T> BufRead for Cursor<T> where T: AsRef<[u8]> {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        let amt = cmp::min(self.pos, self.inner.as_ref().len() as u64);
        Ok(&self.inner.as_ref()[(amt as usize)..])
    }

で、実装的にはinner.as_ref()の部分を返している。

さて、ここで返された&[u8]はなぜ突然&mut出来たのだろうか。

この返り値はrvalueと呼ばれるもの(Rustではvalue expressionという)で、内部バッファを指し示すファットポインタであり、テンポラリなメモリ領域を持つものなのだと理解した。仮に、fill_buf()を読んだまま使わなければ、このファットポインタは即消滅する。消滅させたくなければ、変数に束縛するか、関数に渡すか。そもそも誰の所有物でもないので、

let mut p = self.fill_buf()?
let n = Read::read(&mut p, buf)?;

と書いても問題なく、当然、直に&mutしても問題ないということと理解した。