Option型の値を更新しながら使い回すループにおいて使用するスマートポインター

投稿者: Anonymous

例えば、コマンドライン引数を順番に処理して、以下の処理を行うプログラムを書いたとします。

  • 引数にピリオドが含まれていれば「ファイル名」として扱い、以降の引数に出てくるファイルの「内容」を書き込む対象として変数に保存する
  • 引数にピリオドが含まれていればファイルの「内容」として扱い、最後に保存したファイルに書き込む
    • まだ一つも「ファイル名」が保存されていない場合はエラーとする

(できればこういうことはしない方がいいとは思いますが、これに近い、ちょっとやらざるを得ない状況に陥ったので、単純化した例として挙げました)

いろいろ試行錯誤した結果、次のようになコードになったのですが、ここでm_current_outの型がRefCell<...>にするのが適切なのか、あるいは他の型が良いのか、スマートポインターの使い分けがまだよくわかっていないため教えていただきたいです。

use std::{cell::RefCell, env, fs::File, io::Write};

fn main() {
    // File型を直接使わずに`Box<dyn Write>`にしているのは、本来私が解決したい問題の都合です
    let m_current_out: RefCell<Option<Box<dyn Write>>> = RefCell::new(None);
    for arg in env::args().skip(1) {
        if arg.contains('.') {
            let mut m_current_out_ref = m_current_out.borrow_mut();
            *m_current_out_ref = Some(Box::new(File::create(arg).unwrap()));
        } else {
            let mut m_current_out_ref = m_current_out.borrow_mut();
            writeln!(
                m_current_out_ref
                    .as_deref_mut()
                    .expect("No input file given yet!"),
                "{}",
                arg
            )
            .unwrap();
        }
    }
}

質問は以上ですが、ググラビリティが上がるよう、最初に試してうまく行かなかったコードと、それに対するエラーメッセージを追記しておきます。

コード:

use std::{env, fs::File, io::Write};

fn main() {
    let mut m_current_out: Option<Box<dyn Write>> = None;
    for arg in env::args().skip(1) {
        if arg.contains('.') {
            m_current_out = Some(Box::new(File::create(arg).unwrap()));
        } else {
            writeln!(m_current_out.expect("No input file given yet!"), "{}", arg).unwrap();
        }
    }
}

エラーメッセージ(rustcコマンドの結果):

error[E0382]: use of moved value: `m_current_out`
  --> option-loop-not-working.rs:11:22
   |
6  |     let mut m_current_out: Option<Box<dyn Write>> = None;
   |         ----------------- move occurs because `m_current_out` has type `std::option::Option<std::boxed::Box<dyn std::io::Write>>`, which does not implement the `Copy` trait
...
11 |             writeln!(m_current_out.expect("No input file given yet!"), "{}", arg).unwrap();
   |                      ^^^^^^^^^^^^^ value moved here, in previous iteration of loop

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.

解決

as_ref&Option<T> から Option<&T> に、as_mut&mut Option<T> から Option<&mut T> に変換できるので、これらを使えば消費せずに済みます。

use std::{env, fs::File, io::Write};

fn main() {
    let mut m_current_out: Option<Box<dyn Write>> = None;
    for arg in env::args().skip(1) {
        if arg.contains('.') {
            m_current_out = Some(Box::new(File::create(arg).unwrap()));
        } else {
            let w: Option<&mut Box<dyn Write>> = m_current_out.as_mut();
            let w: &mut Box<dyn Write> = w.expect("No input file given yet!");
            writeln!(w, "{}", arg).unwrap();
        }
    }
}

型をわかりやすくするために変数を分けていますが、ワンライナーでもOKです。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *