ジェネリクスとトレイトオブジェクトの破壊的変更への対処

動機 ジェネリクスとトレイトオブジェクトの抱える問題

壮大たる破壊的変更につながる。

例 Fileインスタンスを持つStreamをジェネリクスに変更する

struct Stream {
    reader: std::fs::File,
}
impl Stream {
    fn new(reader: std::fs::File) {
        Stream {
            reader: reader,
        }
    }
    fn read<'a>(&mut self, buf: &'a mut [u8]) {
        self.reader.read(buf).unwrap();
    }
}

readerフィールドは、Readトレイトを実装していれば良いので、ジェネリクスに変更する。

pub struct Stream<R> {
        reader: R,
}
impl<R: std::io::Read> Stream<R> {
    pub fn new(reader: R) -> Self {
        Stream {
            reader: reader,
        }
    }
    pub fn read<'a>(&mut self, buf: &'a mut [u8]) {
        self.reader.read(buf).unwrap();
    }
}

上記に変更すると、Streamインスタンスをフィールドに含む型もジェネリクス化する必要が生じる。

// XRef から XRef<R>へ
struct XRef<R> {
    stream: Stream<R>:
}

そうするとXRefインスタンスをフィールドに持つ型がある場合は、さらにその型をジェネリクス化する必要があり、壮大な破壊的変更となってしまう。

例 Fileインスタンスを持つStreamをトレイトオブジェクトに変更する

今度は、ライフタイムを付与する必要が発生し、破壊的変更は止まらない

以下ライフタイムを付与しなかった場合、エラーが発生する。

struct Stream {
    reader: Box<dyn std::io::Read>,
}
impl Stream {
    fn new<R: std::io::Read>(reader: R) -> Self {
        Stream {
            reader: Box::new(reader),
        }
    }
    fn read<'a>(&mut self, buf: &'a mut [u8]) {
        self.reader.read(buf).unwrap();
    }
}
/*
error[E0310]: the parameter type `R` may not live long enough
  |
5 |     fn new<R: std::io::Read>(reader: R) -> Self {
  |            -- help: consider adding an explicit lifetime bound...: `R: 'static +`
6 |         Stream {
7 |             reader: Box::new(reader),
  |                     ^^^^^^^^^^^^^^^^ ...so that the type `R` will meet its required lifetime bounds
*/

解決策として、'staticをつける手はあるが、'static以外のライフタイムを持つケースに置いて生成できなくなる。

// 'staticをつける
pub fn new<R>(reader: R) -> Self 
    where
        R: std::io::Read + 'static,
{
    Stream {
        reader: Box::new(reader),
    }
}

// NGとなる
fn make_stream_from_file<'a>(file: &'a std::fs::File) -> Stream {
    Stream::new(file)
}

解決策 トレイトオブジェクト + 具体型からの生成コンストラク

トレイトオブジェクトに変更した場合、コンストラクタのnewメソッドでジェネリクス型パラメーターを要求しているため、ライフタイムが必要となっている。 そこで具体型からのコンストラクタを用意することで、ライフタイムを削除することができる。

// Fileからの生成
pub fn new_from_file(reader: std::fs::File) -> Self {
    Stream {
        reader: Box::new(reader),
    }
}

// Vecからの生成
pub fn new_from_vec_u8(reader: vec<<u8>>) -> Self {
    Stream {
        reader: Box::new(reader),
    }
}

例 Stream

use std::io::{Cursor, Read, Seek};
use std::fs::File;

// 動的ディスパッチでは、複数のトレイトを付ける場合は、新たにトレイトを定義する必要がある
trait ReadSeek: Read + Seek {}
impl ReadSeek for File {}
impl ReadSeek for Cursor<Vec<u8>> {}

pub struct Stream {
    reader: Box<dyn ReadSeek>, // 動的ディスパッチ
}
impl Stream {
    // Fileからの生成
    pub fn new_from_file(reader: File) -> Self {
        Stream {
            reader: Box::new(reader),
        }
    }
    // Cursorからの生成
    pub fn new_from_cursor(reader: Cursor<Vec<u8>>) -> Self {
        Stream {
            reader: Box::new(reader),
        }
    }
    pub fn read<'a>(&mut self, buf: &'a mut [u8]) {
        self.reader.read(buf).unwrap();
    }
}