Дословный перевод
Важно отметить, что статический метод Java не имеет доступа ни к какому состоянию экземпляра. Это означает, что он может быть реплицирован в Rust с помощью функции или связанной функции , ни один из которых не имеет какого-либо состояния. Разница лишь в том, как вы их называете:
fn example() {}
impl Something {
fn example() {}
}
fn main() {
example();
Something::example();
}
Глядя на источник , который вы копируете , он не просто сообщает об ошибке, а имеет такой код:
public class Lox {
static boolean hadError = false;
static void error(int line, String message) {
report(line, "", message);
}
private static void report(int line, String where, String message) {
System.err.println(
"[line " + line + "] Error" + where + ": " + message);
hadError = true;
}
}
Я не эксперт по JVM, но я уверен, что использование такой статической переменной означает, что ваш код больше не является поточно-ориентированным. Вы просто не можете сделать это в безопасном Rust; вы не можете «случайно» сделать небезопасный код памяти.
Самый буквальный перевод этого, который безопасен, использовал бы связанные функции и атомарные переменные:
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
static HAD_ERROR: AtomicBool = ATOMIC_BOOL_INIT;
struct Lox;
impl Lox {
fn error(line: usize, message: &str) {
Lox::report(line, "", message);
}
fn report(line: usize, where_it_was: &str, message: &str) {
eprintln!("[line {}] Error{}: {}", line, where_it_was, message);
HAD_ERROR.store(true, Ordering::SeqCst);
}
}
Вы также можете выбрать более богатые структуры данных для хранения в вашем глобальном состоянии, используя lazy_static и Mutex
или RwLock
, если они вам нужны.
Идиоматический перевод
Хотя это может быть удобно, я не думаю, что такой дизайн хорош . Глобальное состояние просто ужасно. Я бы предпочел использовать внедрение зависимости.
Определите структуру репортера ошибок, в которой указаны нужное вам состояние и методы, и передайте ссылки репортеру ошибок туда, где он должен быть:
struct LoggingErrorSink {
had_error: bool,
}
impl LoggingErrorSink {
fn error(&mut self, line: usize, message: &str) {
self.report(line, "", message);
}
fn report(&mut self, line: usize, where_it_was: &str, message: &str) {
eprintln!("[line {} ] Error {}: {}", line, where_it_was, message);
self.had_error = true;
}
}
fn some_parsing_thing(errors: &mut LoggingErrorSink) {
errors.error(0, "It's broken");
}
В действительности, я бы лучше определил черту для вещей, которые позволяют сообщать об ошибках, и реализовал ее для конкретного типа. Rust делает это хорошо, потому что при использовании этих дженериков разница в производительности отсутствует.
trait ErrorSink {
fn error(&mut self, line: usize, message: &str) {
self.report(line, "", message);
}
fn report(&mut self, line: usize, where_it_was: &str, message: &str);
}
struct LoggingErrorSink {
had_error: bool,
}
impl LoggingErrorSink {
fn report(&mut self, line: usize, where_it_was: &str, message: &str) {
eprintln!("[line {} ] Error {}: {}", line, where_it_was, message);
self.had_error = true;
}
}
fn some_parsing_thing<L>(errors: &mut L)
where
L: ErrorSink,
{
errors.error(0, "It's broken");
}
Есть много вариантов реализации этого, все в зависимости от ваших компромиссов.
- Вы можете выбрать для логгера взять
&self
вместо &mut
, что заставит этот случай использовать что-то вроде Cell
, чтобы получить внутреннюю изменчивость had_error
.
- Вы можете использовать что-то вроде
Rc
, чтобы избежать добавления дополнительных времен жизни в цепочку вызовов.
- Вы можете сохранить регистратор как член структуры вместо параметра функции.
Для дополнительной работы с клавиатурой вы можете проверить свои ошибки . Просто создайте фиктивную реализацию черты, которая сохраняет информацию во внутренние переменные, и передайте ее во время теста.
Мнения, ахой!
стратегия, в которой я вставляю ссылку на некоторую ErrorReporter
структуру как зависимость в Scanner
Да, внедрение зависимостей - удивительное решение для большого числа проблем кодирования.
и Token
структуры
Я не знаю, почему токен должен сообщать об ошибках, но для токенайзера имеет смысл сделать это.
но это кажется мне громоздким. Я не чувствую, что репортер ошибок должен быть частью подписи структуры, я не прав?
Я бы сказал, да, вы не правы; Вы назвали это абсолютной истиной, которой очень мало в программировании.
Конкретно, очень немногие люди заботятся о том, что внутри вашего типа, вероятно, только для того, чтобы быть реализатором. Человек, который создает значение вашего типа, может немного беспокоиться, потому что ему нужно передать зависимости, но это хорошая вещь . Теперь они знают, что это значение может генерировать ошибки, которые им нужно обрабатывать «вне диапазона», в отличие от чтения некоторой документации после того, как их программа не работает.
Еще несколько человек заботятся о фактической подписи вашего типа. Это обоюдоострый клинок. Чтобы добиться максимальной производительности, Rust заставит вас указывать ваши родовые типы и время жизни в сигнатурах ваших типов. Иногда это отстой, но либо выигрыш в производительности того стоит, либо вы можете как-то это скрыть и принять крошечный удар. Это преимущество языка, который дает вам выбор.
Смотри также