概要
Rustのnew()を複数定義することができないため、new()の使いどころは悩みますね。
リストやツリーなど自己参照型の構造体を書いていて、こうしたら良いのでは?というパターンが見えてきたので、まとめます。
ポイント
- new は構造体のすべてのフィールドを明示的に初期化するメソッドとして実装する
- 全フィールドを初期化するメソッドが増減することはないから、new()と同名のものを複数定義せずに済む
- 全フィールドが必須なら、newだけあればよい
- 引数の指定が必要なかったり、すべて省略できる場合は Defaultトレイトを使う
- データのみを指定して初期化する場合は Fromトレイトが役立つ
一部フィールドを初期化する場合に役立つ場合もある - どれにも当てはまらなければ、専用の初期化メソッドを作る
- 決まった使い方しかなければ、分かりやすい名前の初期化メソッドを作ればよい
- パラメーターが多くあるなら、デザインパターンのBuildパターンを適用すればよい
例: Default
初期化でパラメーターが不要なら、 Defaultトレイトを使う。
これにより、個々の new()
の仕様について学習せずに使える。
let mut list: SList<u8> = Default::default();
list.push_front(1);
list.push_front(2);
pub enum SList<T> {
Cons(T, Rc<SList<T>>),
Nil,
}
impl<T> Default for SList<T> {
fn default() -> Self { SList::Nil }
}
例: From と new
- new はすべてのフィールドを明示的に初期化するためのメソッドとして定義するのが良い
- new の効用として、内部の構造を隠ぺいできるメリットがある
例えば、Rc::new(…) を Box::new(…) に変更した場合に修正の影響が少なくできる
- new の効用として、内部の構造を隠ぺいできるメリットがある
- from はデータのみ指定して、それ以外をデフォルトとする場合に都合が良い
newは1つしか宣言できないので、それを補う役割ができる。
pub enum SList<T> {
Cons(T, Rc<SList<T>>),
Nil,
}
impl<T> SList<T> {
pub fn new(v: T, next: SList<T>) -> Self {
SList::Cons(v, Rc::new(next))
}
pub fn push_back(&mut self, v: T) {
let mut cur_slist_ref_mut = self;
while let SList::Cons(_, next_rc_ref_mut) = cur_slist_ref_mut {
// &mut SList<T> <- &mut Rc<SList<T>>
cur_slist_ref_mut = Rc::get_mut(next_rc_ref_mut).unwrap();
}
let _ = std::mem::replace(cur_slist_ref_mut, SList::from(v));
}
pub fn push_front(&mut self, v: T) {
let head_node: SList<T>;
head_node = std::mem::replace(self, SList::Nil);
let _ = std::mem::replace(self, SList::new(v, head_node));
}
}
impl<T> From<T> for SList<T> {
fn from(v: T) -> Self { SList::new(v, SList::Nil) }
}