ジェネリクスとトレイトオブジェクトの破壊的変更への対処
動機 ジェネリクスとトレイトオブジェクトの抱える問題
壮大たる破壊的変更につながる。
例 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(); } }