Использовать черту fmt :: Write или io :: Write для моего записываемого типа? - PullRequest
3 голосов
/ 10 июля 2020

У меня есть структура, которую можно преобразовать в текст с помощью сложной серии вызовов методов, содержащих множество вызовов write!. Этот текст можно записать в файл или в журнал отладки. Я пытаюсь решить, использовать ли fmt::Write или io::Write. Я не могу использовать оба, потому что тогда все методы записи должны быть дублированы.

Упрощенный пример:

impl MyType {
    fn write_it(&self, writer: &mut impl ???) {
//                                       ^ fmt::Write or io::Write?
        self.write_header(writer);
        self.write_contents(writer);
        self.write_footer(writer);
    }

    fn write_header(&self, writer: &mut impl ???) {
        write!(writer, "...")
    }
    // and more...
}

Документы для fmt::Write скажем,

... Это похоже на свойство стандартной библиотеки io :: Write, но оно предназначено только для использования в libcore.

Итак, это наводит меня на мысль, что я должен использовать io::Write. Это (очевидно) будет хорошо работать для таких типов ввода-вывода, как BufWriter. Примечательно, что serde_ json делает это таким образом .

// if I use io::Write, I can do this
my_type.write_it(&mut BufWriter::new(File::create("my-file.txt")?)?;

Я также хочу использовать свой тип с format! и подобными макросами. Поэтому мне нужно реализовать Display. Фактически, не является ли Display фактической чертой для типа, который может быть представлен как String?

// I want to do this
println!("Contents:\n{}", my_type);

// or this
let mut s = String::new();
write!(s, "{}", my_type);

Так что я думаю, что я просто буду использовать ie в своей реализации. с Display. Но вот проблема:

impl Display for MyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.write_it(f)?;
//                    ^ the trait `std::io::Write` is not implemented for `std::fmt::Formatter<'_>`
    }
}

Я могу исправить это, используя impl fmt::Write, но тогда я теряю упомянутую выше возможность ввода-вывода. Между двумя типами нет переходника (я предполагаю, что он был бы, если бы это было предполагаемое использование). Так что я оказался между ними двумя. Какую черту мне следует использовать? Есть ли идиоматический c способ получить функциональность обоих?

Edit: Adapter?

@ ÖmerErden предложил создать адаптер, подобный следующему. Я просто не уверен, какие условия приведут к сбою преобразования UTF-8. Гарантированно ли это преобразование не потерпит неудачу, если я напишу действительный UTF-8 с write!? Мне это кажется слишком рискованным.

struct Adapter<'a> {
    f: &'a mut dyn fmt::Write,
}

impl<'a> io::Write for Adapter<'a> {
    fn write(&mut self, b: &[u8]) -> Result<usize, io::Error> {
        let s = str::from_utf8(b)
            .map_err(|_| io::Error::from(io::ErrorKind::Other))?;
        self.f.write_str(s)
            .map_err(|_| io::Error::from(io::ErrorKind::Other))?;
        Ok(b.len())
    }

    fn flush(&mut self) -> Result<(), io::Error> {
        Ok(())
    }
}

1 Ответ

3 голосов
/ 13 июля 2020

Правильнее всего реализовать Display для вашего типа и не иметь метода write_it вообще. В идеале ваши header, content и footer также реализуют Display, потому что тогда вы можете написать что-то вроде этого:

impl Display for MyType {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}{}", self.header, self.content, self.footer)
    }    
}

Однако, если это невозможно, вы все равно можете написать вручную, как вы это делали в примере кода.

Точнее сказать, что Display - это де-факто черта для типа, который может быть записан на что-то. Преобразование объектов в строки - лишь один из многих вариантов использования Display, но само по себе оно не создает никаких промежуточных строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...