Тип результата
Тип результата (англ. result type) — это монадический тип, используемый в функциональном программировании для хранения возвращаемого значения или кода ошибки. Тип результата предоставляет элегантный способ обработки ошибок без использования механизма исключений: когда функция, способная завершиться с ошибкой, возвращает тип результата, программист вынужден явно рассмотреть оба случая — успех и неудачу — прежде чем получить доступ к ожидаемому результату. Это исключает возможность неверных предположений со стороны программиста.
Тип результата играет ключевую роль в обеспечении сквозной типобезопасности (англ. end-to-end type safety). Он позволяет безопасно передавать данные между различными слоями системы без риска возникновения непредсказуемых исключений, так как возможные ошибки передаются внутри контейнера и гарантированно обрабатываются на нужном этапе цепочки вызовов[1].
Примеры
- В C++ тип результата определён стандартной библиотекой как
std::expected<T, E>[2]. - В C# и платформе .NET отсутствует стандартная реализация типа результата или размеченных объединений. Ведётся обсуждение добавления нативных размеченных объединений в язык, а на данный момент используются сторонние библиотеки, такие как OneOf и FluentResults[3][4].
- В Elm тип результата определяется как
type Result e v = Ok v | Err e[5]. - В Haskell для этой цели по соглашению используется тип
Either, определённый стандартной библиотекой какdata Either a b = Left a | Right b, гдеa— тип ошибки, аb— тип результата[6]. - В Java тип результата не входит в стандартную библиотеку, но реализован в сторонних библиотеках, например, в result4j (интерфейс
Result<R, E>, аналогичныйResult<T, E>в Rust) и vavr (интерфейсEither<L, R>, какEither a bв Haskell). Поскольку Java и Kotlin совместимы, Java может использовать типResultиз Kotlin. - В Kotlin тип результата определяется стандартной библиотекой как
value class Result<out T>[7]. - В OCaml тип результата представлен стандартной библиотекой как
type ('a, 'b) result = Ok of 'a | Error of 'b type[8]. - В Python тип результата отсутствует в стандартной библиотеке, но реализован в сторонних библиотеках, таких как returns и result.
- В Rust тип результата определён стандартной библиотекой как
enum Result<T, E> { Ok(T), Err(E) }[9][10]. - В Scala стандартная библиотека также определяет тип
Either[11], однако в Scala по-прежнему широко применяется более традиционная обработка исключений. - В Swift тип результата определён стандартной библиотекой как
@frozen enum Result<Success, Failure> where Failure : Error[12]. - В TypeScript тип результата реализуется с помощью сторонних библиотек, таких как Effect-TS и neverthrow[13].
- В V тип результата реализован на уровне языка через возвращаемое значение
!T. Например:fn my_function() !string { ... }. Обработка ошибок в V.
C++
Класс expected<T, E> использует std::unexpected() для возврата значения типа E и может возвращать T напрямую.
import std;
using std::expected;
using std::ifstream;
using std::string;
using std::stringstream;
using std::unexpected;
using std::filesystem::path;
enum class FileError {
MissingFile,
NoPermission,
// другие ошибки
};
expected<string, FileError> loadConfig(const path& p) noexcept {
if (!std::filesystem::exists(p)) {
return unexpected(FileError::MissingFile);
}
ifstream config{p};
stringstream buffer;
if (!config.is_open()) {
return unexpected(FileError::NoPermission);
}
buffer << config.rdbuf();
config.close();
return buffer.str();
}
int main(int argc, char* argv[]) {
path p{/* путь к файлу */};
if (const expected<String, FileError> s = loadConfig(p); s.has_value()) {
std::println("Config contents: {}", s.value());
} else if (s.error() == FileError::MissingFile) {
std::println("Error: path {} not valid or missing!", p);
} else if (s.error() == FileError::NoPermission) {
std::println("Error: no permission to read file at path {}!", p);
} else {
std::unreachable();
}
}
Rust
Объект типа результата имеет методы is_ok() и is_err().
const CAT_FOUND: bool = true;
fn main() {
let result: Result<(), String> = pet_cat();
if result.is_ok() {
println!("Great, we could pet the cat!");
} else {
let error: String = result.unwrap_err();
println!("Oh no, we couldn't pet the cat: {}", error);
}
}
fn pet_cat() -> Result<(), String> {
if CAT_FOUND {
Ok(())
} else {
Err(String::from("The cat is nowhere to be found!"))
}
}
В редакции Rust 2024 года были изменены правила вывода типа ! (never type): теперь приведение типов возвращается к ! вместо (), что напрямую влияет на использование оператора ? (тип успешного результата теперь выводится как !)[14]. Также ведётся работа над редизайном и стабилизацией трейта Try, определяющего поведение оператора ? (добавлены ассоциированные типы Output и Residual, а также трейт FromResidual)[15]. Кроме того, продолжает развиваться экспериментальная возможность блоков try, позволяющая использовать оператор ? внутри специальных блоков для раннего возврата ошибок в функциях, не возвращающих Result или Option[16].
Vlang
Тип Error представляет собой интерфейс для iError.
Типы Option (?T) и Result (!T) в языке строго разделены, при этом интерфейс IError для пользовательских ошибок остался без изменений[17].
const cat_found = true
fn main() {
cat_name := get_pet_cat_name() or {
println("Oh no, we couldn't pet the cat!")
exit(1)
}
println('Great, we could pet the cat ' + cat_name)
}
fn get_pet_cat_name() !string {
if cat_found { return 'Max' }
else { return error('the cat is nowhere to be found') }
}
TypeScript
В TypeScript реализация монадического типа данных (Result) осуществляется с помощью сторонних библиотек, таких как Effect-TS и neverthrow. Библиотека neverthrow предоставляет легковесный тип Result для отслеживания ошибок. Effect-TS симулирует эффекты на основе монад, используя структуру Effect<A, E, R> для отслеживания успешных результатов, ошибок и зависимостей на уровне типов. Для автоматического вывода типов в Effect-TS применяются генераторы (конструкция Effect.gen и yield*), собирающие типы из всех переданных эффектов, а также цепочки flatMap, которые связывают вычисления с сохранением информации о типах на каждом этапе[13].
Безопасное логирование
Для предотвращения утечек чувствительных данных из полей ошибок применяются методы безопасного логирования. Лучшие практики включают извлечение только необходимой информации и отказ от прямого логирования полных текстов ошибок. Вместо этого рекомендуется логировать обобщённые сообщения (например, «error detected»), а также использовать коды ошибок или идентификаторы[18].