Обработка ошибок в Rust для разработчиков JavaScript

Для инженеров JavaScript, изучающих Rust, понимание ошибок и способов их обработки является сложной концепцией для понимания.

На первый взгляд, JavaScript и Rust не совсем одинаковы в том, что касается обработки ошибок — на самом деле они кажутся полярными противоположностями.

Но если вы посмотрите немного глубже, между обработкой ошибок в Rust и JavaScript есть много общего. Подобно JavaScript, Rust обычно имеет те же типы ошибок.

И в обоих этих контекстах у вас есть немного другой способ обработки ошибок и возврата или восстановления после ошибок.

В любом языке, если у вас есть фатальная ошибка, программа остановится и выйдет с каким-то кодом состояния выхода. И это нормально, вы на самом деле хотите, чтобы ваши программы громко и внезапно давали сбой.

Гораздо лучше остановиться и загореться, чем позволить программе молча выйти из строя и продолжать работать в ошибочном состоянии. В качестве альтернативы, если произошел сбой и результат процесса не требовался для продолжения, вы можете зарегистрировать ошибку и продолжить программу, возможно, также отправив сообщение об ошибке в систему отслеживания ошибок.

Исходный код и предисловие

Для краткости я предполагаю, что вы прочитали Руководство по обработке ошибок в Rust, знаете, что такое Option и Result, и имеете общее представление о них.

Цель этой статьи — помочь вам связать перечисление Result с try...catch блоками в JavaScript.

Если вы хотите познакомиться с Rust в отношении программ командной строки, модульного тестирования и непрерывной интеграции, ознакомьтесь с этой статьей о создании RPG-игры Rust roguelike на консоли.

Исходный код Примера А доступен здесь, на GitHub, вместе с двумя оболочками, которые запускают каждую функцию. Код для примера B можно легко применить к тем же функциям.

Обертки для запуска кода

Мы создадим функции, которые загружают строку JSON в нативный объект как в Rust, так и в JavaScript.

В рамках этого у нас есть функция main, которая дважды вызывает вспомогательные функции, один раз с допустимым JSON и один раз с недопустимым JSON. Они предназначены для обеспечения точки входа в наш код.

Оболочка: JavaScript вызывает loadJson

function main() {
    console.log("-- Parsing valid JSON data --");
    loadJson(
        `{
            "name": "Fido",
            "hasSpots": false,
            "items": {
                "snacks": [
                    "biscuit",
                    "beef chew"
                ]
            }
        }`
    );
​
    console.log("-- Parsing invalid JSON data --");
    loadJson(
        `
            "name": "Fido",
            "hasSpots": false,
            "items": {
                "snacks": [
                    "biscuit",
                    "beef chew"
                ]
            }
        }`
    );
}

Обертка: Rust вызывает load_json (см. Cargo.toml в репозитории для получения информации о крейте json)

use json;
​
fn main() {
    println!("-- Parsing valid JSON data --");
    load_json(
        r#"
{
    "name": "Fido",
    "hasSpots": false,
    "items": {
        "snacks": [
            "biscuit",
            "beef chew"
        ]
    }
}
"#,
    );
​
    println!("-- Parsing invalid JSON data --");
    load_json(
        r#"
    "name": "Fido",
    "hasSpots": false,
    "items": {
        "snacks": [
            "biscuit",
            "beef chew"
        ]
    }
}
"#,
    );
}

В наших примерах мы пытаемся преобразовать строку JSON в пригодный для использования объект во время выполнения. Этот файл JSON может находиться на жестком диске или предоставляться пользователем.

Далее мы рассмотрим код, используемый для обработки недопустимого JSON и корректного восстановления как в Rust, так и в JavaScript.

Пример A: Устраненная ошибка

В примере A мы считаем данные JSON вспомогательными и не обязательными для завершения программы, возможно, это необязательный файл от пользователя, который переопределяет значения по умолчанию. В случае, если файл поврежден или отсутствует, мы все равно можем продолжить работу с приложением.

Устраненная ошибка: JavaScript

function loadJson(jsonString) {
    let data;
    try {
        data = JSON.parse(jsonString);
    } catch (error) {
        console.log("\tError parsing JSON: %s", error);
        data = {};
    }
​
    console.log("JSON data retrieved\n\t%o", data);
}

Устраненная ошибка: Rust

fn load_json(json_string: &str) {
    let _data = json::parse(json_string).unwrap_or_else(|error| {
        println!("\tError parsing JSON: {}", error);
        return json::JsonValue::new_object();
    });
​
    println!("JSON data retrieved\n\t{}", _data);
}

Далее мы рассмотрим изменение кода для остановки программы, как в Rust, так и в JavaScript.

Пример B: Выход из строя

В примере B мы считаем данные JSON обязательными и, следовательно, необходимыми для завершения программы. В случае, если файл поврежден или отсутствует, мы регистрируем сообщение об ошибке, а затем отправляем программу на гибель с запрограммированным сбоем.

Ошибка выхода: JavaScript

function loadJson(jsonString) {
    let data;
    try {
        data = JSON.parse(jsonString);
    } catch (error) {
        console.log("\tError parsing JSON: %s", error);
        throw new Error('Invalid JSON');
    }
​
    console.log("JSON data retrieved\n\t%o", data);
}

Результаты:

-- Parsing valid JSON data --
JSON data retrieved
    { name: 'Fido',
  hasSpots: false,
  items: { snacks: [ 'biscuit', 'beef chew', [length]: 2 ] } }
-- Parsing invalid JSON data --
    Error parsing JSON: SyntaxError: Unexpected token : in JSON at position 10
/rs-js-errors/src/main.js:37
        throw new Error('Invalid JSON');
        ^
​
Error: Invalid JSON
    at loadJson (/rs-js-errors/src/main.js:37:9)
    at main (/rs-js-errors/src/main.js:17:2)
    (stack continues ...)

Выход с ошибкой: Rust

fn load_json(json_string: &str) {
    let _data = json::parse(json_string).unwrap_or_else(|error| {
        println!("\tError parsing JSON: {}", error);
        panic!("Invalid JSON")
    });
​
    println!("JSON data retrieved\n\t{}", _data);
}

Результаты:

-- Parsing valid JSON data --
JSON data retrieved
    {"name":"Fido","hasSpots":false,"items":{"snacks":["biscuit","beef chew"]}}
-- Parsing invalid JSON data --
    Error parsing JSON: Unexpected character: : at (2:11)
thread 'main' panicked at 'Invalid JSON', src/main.rs:39:3
​

Вы можете видеть, что в обоих случаях мы получили полезное сообщение об ошибке, номер строки возникновения ошибки и трассировку стека относительно файла и запущенного процесса.

Вывод

Как бы вы выбрали между этими двумя?

Вариант 1: необязательный процесс/данные

В первой программе, Пример A, JSON может быть расширенными параметрами, которые могут быть у пользователя, а могут и не быть. В этом ключе вы можете затем предположить в своей программе, что если пользовательские параметры не загружаются или не существуют, они либо повреждены, либо недоступны.

Поскольку они просто изменяют настройки по умолчанию, вы можете зарегистрировать сообщение об ошибке (возможно, даже для пользователя), а затем продолжить работу с приложением.

Случай 2: Требуемый процесс/данные

Во второй программе, Пример B, данные JSON являются необходимой конфигурацией, необходимой для запуска программы.

Таким образом, в этом случае вы как программист можете решить, что если конфигурация приложения не существует, вы собираетесь остановить программу и немедленно выдать сообщение об ошибке, чтобы сообщить пользователю, что вы не хотите запускать программу, если данные JSON существуют.