Runtime error rust

Error cargo run Compiling stack_overflow v0.1.0 (/d/stack_overflow) Finished dev [unoptimized + debuginfo] target(s) in 7.08s Running `target/debug/stack_overflow` thread 'main' has overflo...

Error

cargo run
   Compiling stack_overflow v0.1.0 (/d/stack_overflow)                                                                        
    Finished dev [unoptimized + debuginfo] target(s) in 7.08s                                                                 
     Running `target/debug/stack_overflow`

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Аварийный останов (стек памяти сброшен на диск)

Rust Version

RUST STABLE:
rustc 1.31.1 (b6c32da9b 2018-12-18)

RUST NIGHTLY:
rustc 1.33.0-nightly (9eac38634 2018-12-31)

Code

use std::fmt::Write;
use std::ops::Deref;
use std::fmt;

//////The structure stores in itself the changeable reference and has to clean data in "drop" of structure.
#[derive(Debug)]
pub struct DebugSliceMutString<'a>(&'a mut String);



//Stack overflow <----
//assert_eq!(&null.my_debug(&mut string).unwrap(), "=");
impl<'a> PartialEq<str> for DebugSliceMutString<'a> {   
     fn eq(&self, other: &str) -> bool {
          self == other
     }
}



//Stack overflow <----
//assert_eq!(null.my_debug(&mut string).unwrap(), "=");
impl<'a> PartialEq<&str> for DebugSliceMutString<'a> {
     fn eq(&self, other: &&str) -> bool {
          self == other
     }
}

//If to take 'deref' that there is no overflow of a stack!!
//assert_eq!(*null.my_debug(&mut string).unwrap(), "=");
impl<'a> Deref for DebugSliceMutString<'a> {
     type Target = String;
     
     fn deref(&self) -> &String {
          self.0
     }
}

impl<'a> Drop for DebugSliceMutString<'a> {
     fn drop(&mut self) {
          self.0.clear()
     }
}



//MyTrait
pub trait MyDebug {
     fn my_debug<'a>(&self, w: &'a mut String) -> Result<DebugSliceMutString<'a>, fmt::Error>;
}

impl MyDebug for (usize, usize) {
     fn my_debug<'a>(&self, w: &'a mut String) -> Result<DebugSliceMutString<'a>, fmt::Error> {
          write!(w, "{}={}", self.0, self.1)?;

          Ok( DebugSliceMutString(w) )
     }
}

//


fn main() {
	//The buffer for an exception of frequent reallocations of memory
	let mut string = String::with_capacity(9);

	//Any other type, in this case (usize, usize) is more convenient
	let null =	(10, 20);
	

	// !!!!!!!
	//thread 'main' has overflowed its stack
	//fatal runtime error: stack overflow
	//Аварийный останов (стек памяти сброшен на диск)
	assert_eq!(	null.my_debug(&mut string).unwrap(), "=");


	// !!!!!!!
	//If to use Deref that there is no overflow of a stack!!
	//assert_eq!(	*null.my_debug(&mut string).unwrap(), "=");


	// !!!!!!!OR
	//thread 'main' has overflowed its stack
	//fatal runtime error: stack overflow
	//Аварийный останов (стек памяти сброшен на диск)
	//
	//let a = null.my_debug(&mut string).unwrap()
	//	.eq(&"=");
	//

	//
	println!("string, {}, capacity: {}", string, string.capacity());
}

Run

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5bacc9a73f0ae7a38100da9d196b08d0

Содержание

  1. fatal runtime error: Rust cannot catch foreign exceptions when syncing target=#6745651 #3817
  2. Comments
  3. «fatal runtime error: Rust cannot catch foreign exceptions» with remove_dir on Mac #301
  4. Comments
  5. System details
  6. What you did (as detailed as you can)
  7. What you expected
  8. What happened
  9. «fatal runtime error: Rust cannot catch foreign exceptions» with `remove_dir` on Mac about notify HOT 7 CLOSED
  10. Comments (7)
  11. Related Issues (20)
  12. Recommend Projects
  13. React
  14. Vue.js
  15. Typescript
  16. TensorFlow
  17. Django
  18. Laravel
  19. Recommend Topics
  20. javascript
  21. server
  22. Machine learning
  23. Visualization
  24. Recommend Org
  25. Facebook
  26. Microsoft
  27. crashes on macOS with iced #3
  28. Comments
  29. Text drawing/measurement cause fatal error on Alpine LInux #17
  30. Comments

fatal runtime error: Rust cannot catch foreign exceptions when syncing target=#6745651 #3817

  • It would help if you submit info about the system you are running, e.g.: operating system, kernel version, amount of available memory and swap, etc.
  • Logs could be very helpful. If possible, submit the whole log. Please format it as code blocks .
  • Describe the role your node plays, e.g. validator, full node or light client.
  • Any command-line options were passed?
    —name=’hashinFrog-polkadot-1′ —validator

The text was updated successfully, but these errors were encountered:

Got another issue bringing it back up. Testing out v0.9.8 on ubuntu16 with the same data from the previous failed sync

The second error is related to the parachain db. Removing should fix it.

I thought I was just trying to fresh sync the parachain db. Removing it seems to hit the error again.

I updated to the suggested Ubuntu18 and I am getting a similar error as the original error on a different block. I can’t seem to run the exact docker image supplied by Parity on my cloud host due to the possibly the version of Ubuntu supported, but I am getting it really close to what they have.

I will be posting another error soon for a different block.using my newest docker setup

Using the latest v0.9.10 code + updating to Ubuntu18, I’m still unable to sync the blockchain.

New error off syncing with the latest client.

I’ve set RUST_BACKTRACE=1 as an env variable on the whole environment, so that should be set for any subprocess and I’m still seeing the same error.

Is there a good version to use that would allow me to sync the blockchain from initial install? Am I the only one having trouble syncing the full chain so I can get my validator up and working?

Is there something wrong with my Dockerfile that is forcing this to cause an exception where no others are getting execution?

I’ve done my best to follow the tutorial and look at the existing dockerfile to create my setup. It’s pretty simple and straightforward. Next step would be for me to figure out where in code this is happening.

@bkchr I see that 12 hours of testing is required for new releases, but that is not on the parachain from what I can tell, and it»s not clear it’s using a fresh sync versus an existing DB.

This has been a roadblocker for me. It would be great to know if should be using a different chain, but I literally have this thing processing about 2/3rd’s of the blockchain and then craps out

Источник

«fatal runtime error: Rust cannot catch foreign exceptions» with remove_dir on Mac #301

System details

OS/Platform name and version: macOS Catalina ( Darwin Kernel Version 19.6.0: Tue Jan 12 22:13:05 PST 2021; root:xnu-6153.141.16

Rust version (if building from source): rustc —version : rustc 1.51.0 (2fd73fabe 2021-03-23)

Notify version (or commit hash if building from git): Current main (4a3f5a3). I observed the same behavior using the debounced notify::watcher API in Notify 4.0.16.

I’m not running as a privileged user, or in a container, or in a VM.

Filesystem type and options: Here’s the output of mount :

On this system, the directory that is actually watched is «/var/folders/vr/my8x9tmn4y7cdjcj8f7jw2gr0000gn/T/.tmprPcUcB» , and /var is a symlink to private/var .

What you did (as detailed as you can)

  1. clone the notify repo
  2. make a file tests/race-with-remove-dir.rs with the contents below
  3. cargo test

What you expected

The test might pass, fail, or panic. I’m fine with any of those outcomes.

What happened

The text was updated successfully, but these errors were encountered:

Can you rerun this with RUST_BACKTRACE=1 environment set, so we get a stacktrace ?

This has to be in the bindings for macos, so your code sadly doesn’t tell much. I’ll see if I can get a macOS VM running, as we currently don’t have mac testers for debugging.

I’ll keep debugging. RUST_BACKTRACE=1 unfortunately gives no information, as this isn’t caused by a normal Rust panic.

The error message comes from here.

Running under lldb causes the crash not to happen. Instead I get this, suggesting a race condition:

OK, by tinkering with the timing, I got a stack trace. But it’s not much use, as this stack is captured after stack unwinding already happened.

The crashing thread (shown below) is the one created by the thread::spawn call in the test. There are two other threads:

  • the main thread, which is waiting for all tests to finish running; and
  • the test_race_with_remove_dir thread, which is sleeping in thread::sleep() .

Hm, sad but thanks for investigating. I guess we’ll need to investigate whether we’re doing something wrong in fsevent or the fsevent(-sys) crate is doing something wrong.

Another hint is from your previous run with lldb, that still threw an error at fsevent.rs:327 if I’m right, for an option unwrap. So we didn’t crash at another point but tried to get information about a dead path when trying to recursively add a newly added folder to the watcher. I guess we’re holding or requesting data for a dead descriptor when a rapid create+delete occurs.

I think that is basically right. I have a patch that seems to fix it. PR coming soon.

Источник

«fatal runtime error: Rust cannot catch foreign exceptions» with `remove_dir` on Mac about notify HOT 7 CLOSED

I’ll keep debugging. RUST_BACKTRACE=1 unfortunately gives no information, as this isn’t caused by a normal Rust panic.

The error message comes from here.

Running under lldb causes the crash not to happen. Instead I get this, suggesting a race condition:

0xpr03 commented on January 17, 2023

Can you rerun this with RUST_BACKTRACE=1 environment set, so we get a stacktrace ?

This has to be in the bindings for macos, so your code sadly doesn’t tell much. I’ll see if I can get a macOS VM running, as we currently don’t have mac testers for debugging.

jorendorff commented on January 17, 2023

OK, by tinkering with the timing, I got a stack trace. But it’s not much use, as this stack is captured after stack unwinding already happened.

The crashing thread (shown below) is the one created by the thread::spawn call in the test. There are two other threads:

  • the main thread, which is waiting for all tests to finish running; and
  • the test_race_with_remove_dir thread, which is sleeping in thread::sleep() .

0xpr03 commented on January 17, 2023

Hm, sad but thanks for investigating. I guess we’ll need to investigate whether we’re doing something wrong in fsevent or the fsevent(-sys) crate is doing something wrong.

0xpr03 commented on January 17, 2023

Another hint is from your previous run with lldb, that still threw an error at fsevent.rs:327 if I’m right, for an option unwrap. So we didn’t crash at another point but tried to get information about a dead path when trying to recursively add a newly added folder to the watcher. I guess we’re holding or requesting data for a dead descriptor when a rapid create+delete occurs.

jorendorff commented on January 17, 2023

I think that is basically right. I have a patch that seems to fix it. PR coming soon.

0xpr03 commented on January 17, 2023

Thanks for the PR. I’ll merge this tomorrow and do a backport for v4.

  • Consider use dedicated configuration interface for every single watcher implementation.
  • Remove Usage section from readme HOT 1
  • No events emitted when watching /proc/self/mounts HOT 2
  • How can I get the events from the files that are not closed on windows?
  • `Function not implemented (os error 38)` when running on docker in M1 MacOS HOT 10
  • Notify only catches one change. HOT 1
  • Reminder: the debouncer lib.rs still has debug println HOT 2
  • Rescan flag is correctness hazard HOT 12
  • Overhaul Wiki
  • debouncer-mini use park_timeout to allow faster shutdown
  • Not working on aarch64/android HOT 13
  • Ignored config HOT 1
  • docs todo tracking
  • Event handler callback called twice? HOT 4
  • Compilation fails on GH Actions on MacOS when using fsevents HOT 5
  • Help with migrating from v4 HOT 3
  • V5 migration HOT 5
  • Explicit poll/flush operation HOT 2
  • Document whether Event.paths are absolute/relative/either HOT 2
  • Handle BSD Too many open files in system (os error 23)

Recommend Projects

React

A declarative, efficient, and flexible JavaScript library for building user interfaces.

Vue.js

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Typescript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

TensorFlow

An Open Source Machine Learning Framework for Everyone

Django

The Web framework for perfectionists with deadlines.

Laravel

A PHP framework for web artisans

Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

javascript

JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

Some thing interesting about web. New door for the world.

server

A server is a program made to process requests and deliver data to clients.

Machine learning

Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

Visualization

Some thing interesting about visualization, use data art

Some thing interesting about game, make everyone happy.

Recommend Org

Facebook

We are working to build community through open source technology. NB: members must have two-factor auth.

Microsoft

Open source projects and samples from Microsoft.

Источник

crashes on macOS with iced #3

I tried to use AsyncFileDialog in an iced app on macOS, but when trying to use the dialog, the app crashes, printing:

This happens on 0.1.2 as well as master.

The text was updated successfully, but these errors were encountered:

That’s probably something threading related, I’ll see what can be done in this regard.

  1. Does it crash on creation or later?
  2. If it crashes right away, on which tread does it happen? Like. On which thread pick_file fn is called?
    (you can run simple println!(«<:?>«, std::thread::current().id()); right before calling rfd to check this)
  3. Simple example would also be helpful.

I was pretty sure that iced always calls update on the main thread as it is direct result of winit event loop callback being called. But maybe I was wrong in this regard. (Judging by examples in iced repo, it is true)

Is this how you use it in iced ?

And one more question:
Kinda related to previous question, when does the dialogue crashes? Does the dialogue open at all? Does it crash after selecting a file? After closing? etc.
If it crashes after close then I probably know what’s the problem, otherwise it will be quite tricky one to figure out.

Here’s a minimal example, with the async code being called from a regular Command , as is the usual case in iced apps.

The file dialog window never appears, and the output is:

So the crash happens in pick_file , not new .

My bad, I should have documented it better, there is no way to spawn dialogs from other threads in Mac OS, you have to spawn it on the main thread and await on it in the threaded executor. That’s how Mac OS system API is designed nothing that I can do about it. So in this case dialog should be created in the update, and returned future should be moved into the async block where it can be awaited.

  • Document it better
  • Add thread id asset so app crashes in rust land and returns a user-friendly error instead of macOS exception that is not human-readable

@fenhl Does it solve your problem?
If so, I’ll proceed to add some additional checks and docs for this and close the issue

It fixes the minimal example above, I’ll check if it works in the actual app I’m writing too.

Ok, thanks for the info.

I found a way to work around it, so if everything goes well, soon it will be possible to spawn async dialogues anywhere.

But it is only possible in windowed applications (those have internal macOS event loop, that I can use)
So I don’t want to make it a defacto/recommended way of doing things, as I want the API to be the same for all use-cases.

I will try to integrate it anyway, just in case main-thread limitation is unacceptable for someone (for example, there can be a GUI framework out there that does not allow users to control on which thread their code is executed)

Источник

Text drawing/measurement cause fatal error on Alpine LInux #17

Trying to run it in Docker:

It basically works (using code from readme), but «fillText() or measureText()«` call throws error:

I suppose it can be a problem with some system dependencies.
I tried Ubuntu in Docker and it works:

The text was updated successfully, but these errors were encountered:

I suspect the problem running on Alpine is related to the underlying bindings having been compiled for GNU libc (see rust-skia/rust-skia#356) and your symlink to the musl libc seems to be giving it indigestion. The discussion over there makes it sound like building the rust components from source is possible (with some modifications to support musl), so maybe it would make sense to generate binaries for skia-canvas that are libc-specific.

If you feel like experimenting, I’d be very curious to see how an npm run build && npm test within the module repo would go on Alpine.

I searched a lot and also found that issue. But I’m pretty far from Rust and not a strong Docker user.

Anyway, I tried to build skia-canvas in the Docker container.
Just run it and then played inside using Google to find solutions:

I used the cargo c command. It took a lot of time and everything was green, but

If I change crate-type in Cargo.toml to staticlib (just for test, I’m not sure I understand how it works) — build process works until the same error with not found unknown-linux-musl-textlayout-static binaries.

I need to run the app asap and it looks like skia-canvas solves all problems I have with another drawing library.
On node:14-slim and node:14 I got the error:

I suppose I have to compile lib in that environment to solve it. Quickly tried, it compiles and passes tests. So multi-stage build can be used. But I don’t stick with the node version, so this Dockerfile works for me:

Sorry for too much verbose explanation, maybe it will be helpful for somebody else.

I’ll be happy to try something more to run it on alpine if you have ideas.

Источник

Cover image for How to Handle Errors in Rust: A Comprehensive Guide

Nathan

Nathan

Posted on Dec 2, 2022

• Updated on Dec 15, 2022

Rust community constantly discusses about error handling.. In this article I will try to explain what is it then why, and how we should use it.

Purpose of Error Handling

Error handling is a process that helps to identify, debug, and resolve errors that occur during the execution of a program.
It helps to ensure the smooth functioning of the program by preventing errors from occurring and allows the program to continue running in an optimal state.
Error handling also allows users to be informed of any problems that may arise and take corrective action to prevent the errors from happening again in the future.

What is a Result?

Result is a built-in enum in the Rust standard library.
It has two variants Ok(T) and Err(E).

Image description

Result should be used as a return type for a function that can encounter error situations.
Ok value is return in case of success or an Err value in case of an error.

Implementation of Result in a function.

Image description

What is Error Handling

Sometimes we are using functions that can fail, for example calling an endpoint from an API or searching a file. These type of function can encounter errors (in our case the API is not reachable or the file is not existing).
There are similar scenarios where we are using Error Handling.

Image description

Explained Step by Step

  • A Result is the result of the read username from file function.
    It follows that the function’s returned value will either be an Ok that contains a String or an Err that contains an instance of io::Error.

There is a call to «File::open» inside of read username from file, which returns a Result type.

  • It can return an Ok
  • It can return an Err

Then the code calls a match to check the result of the function and return the value inside the ok in the case the function was successful or return the Error value.

In the second function read_to_string, the same principle is applied, but in this case we did not use the keyword return as you can see, and we finally return either an OK or an Err.

So you may ask: On every result type I have to write all these Match block?

So hopefully there is a shortcut :)

Image description

What is the Question Mark- Propagation Error?

According to the rust lang book:

The question mark operator (?) unwraps valid values or returns erroneous values, propagating them to the calling function. It is a unary postfix operator that can only be applied to the types Result and Option.

Let’s me explain it.

Question mark (?) in Rust is used to indicate a Result type. It is used to return an error value if the operation cannot be completed.
For example, in our function that reads a file, it can return a Result type, where the question mark indicates that an error might be returned if the file cannot be read, or in the other hand the final result.
In other words, used to short-circuit a chain of computations and return early if a condition is not met.

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

Enter fullscreen mode

Exit fullscreen mode

Every time you see a ?, that’s a possible early return from the function in case of Error, else , f will hold the file handle the Ok contained and execution of the function continues (similary to unwrap function).

Why use crates for Handle errors?

Standard library does not provide all solutions for Error Handling..
In fact, different errors may be returned by the same function, making it increasingly difficult to handle them precisely.
Personal anecdote, in our company we developed Cherrybomb an API security tool written in Rust, and we need to re-write a good part of it to have a better errors handling.

For example:

Image description

Or the same message error can be displayed multiples times.

Image description

This is why we need to define our own custom Error enum.

Image description

Then our function will look like:

Image description

Customize Errors

Thiserror focuses on creating structured errorsand has only one trait that can be used to define new errors:

Thiserror is an error-handling library for Rust that provides a powerful yet concise syntax to create custom error types.

In the cargo toml:

[dependencies]
thiserror = "1.0"

It allows developers to create custom error types and handlers without having to write a lot of boilerplate code.

Thank to thiserror crate, we can customize our error messages.

Image description

It also provides features to automatically convert between custom error types and the standard error type. We will see it in the next Chapter with Dynamic Error.

  • Create new errors through #[derive(Error)].
  • Enums, structs with named fields, tuple structs, and unit structs are all possible.
  • A Display impl is generated for your error if you provide #[error(«…»)] messages on the struct or each variant of your enum and support string interpolation.

Example taken from docs.rs:

Image description

Dealing Dynamic Errors handling

If you want to be able to use?, your Error type must implement the From trait for the error types of your dependencies. Your program or library may use many dependencies, each of which has its own error you have two different structs of custom error, and we call a function that return one specific type.
For example:
Image description

So when we call our main function that return a ErrorA type, we encounter the following error:

Image description

So one of the solution is to implement the trait From<ErrorB> for the struct ErrorA.

Our code looks like this now:

Image description

Another solution to this problem is to return dynamic errors.
To handle dynamic errors in Rust, in the case of an Err value, you can use the box operator to return the error as a Box (a trait object of the Error trait). This allows the error type to be determined at runtime, rather than at compile time, making it easier to work with errors of different types.

The Box can then be used to store any type of Error, including those from external libraries or custom errors. The Box can then be used to propagate the Error up the call stack, allowing for appropriate handling of the error at each stage.

Image description

Thiserror crate

In order to have a code clearer and soft let’s use thiserror crate.
The thiserror crate can help handle dynamic errors in Rust by allowing the user to define custom error types. It does this through the #[derive(thiserror::Error)] macro. This macro allows the user to define a custom error type with a specific set of parameters, such as an error code, a message, and the source of the error. The user can then use this error type to return an appropriate error value in the event of a dynamic error. Additionally, the thiserror crate also provides several helpful methods, such as display_chain, which can be used to chain together multiple errors into a single error chain.
In the following we have created our error type ErrorB , then we used the From trait to convert from ErrorB errors into our custom ErrorA error type. If a dynamic error occurs, you can create a new instance of your error type and return it to the caller. See function returns_error_a() in line 13.

Image description

Anyhow crate

anyhow was written by the same author, dtolnay, and released in the same week as thiserror.
The anyhow can be used to return errors of any type that implement the std::error::Error trait and will display a nicely formatted error message if the program crashes.
The most common way to use the crate is to wrap your code in a Result type. This type is an alias for the std::result::Result<T, E> type, and it allows you to handle success or failure cases separately.

Image description

When an error occurs,for example you can use the context() method to provide more information about the error, or use the with_chain() method to chain multiple errors together.
The anyhow crate provides several convenient macros to simplify the process of constructing and handling errors. These macros include the bail!() and try_with_context!()macros.
The former can be used to quickly construct an error value, while the latter can be used to wrap a function call and automatically handle any errors that occur.

Comparison

The main difference between anyhow and the Thiserror crate in Rust is the way in which errors are handled. Anyhow allows for error handling using any type that implements the Error trait, whereas Thiserror requires you to explicitly define the error types using macros.

Anyhow is an error-handling library for Rust that provides an easy way to convert errors into a uniform type. It allows to write concise and powerful error-handling code by automatically converting many different types of errors into a single, common type.

In conclusion,in Cherrybomb we choose to combining the two, in order to create a custom error type with thiserror and managed it by the anyhow crate.

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

Содержание

  1. Что делать с ошибкой?

  2. Немного о синтаксисе Rust

  3. Обработка ошибок в Rust

  4. Полезные библиотеки

  5. Заключение

Что делать с ошибкой?

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

  1. Завершить работу программы. Это самый простой вариант, не требующий больших усилий от разработчика. Он применим в случаях, когда ошибка не позволяет программе корректно выполнять свои функции. В качестве примера можно рассмотреть приложение, представляющее собой обёртку над некоторой динамической библиотекой. Скажем, графический интерфейс. Приложение поставляется с этой библиотекой и не несёт какой-либо пользы в отрыве от неё. Разумно предположить, что приложение не должно работать без этой библиотеки. Поэтому, вполне обосновано, при ошибке загрузки библиотеки, прерывать работу приложения.

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

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

Ошибки, после которых приложение должно завершить работу называют неустранимыми. Остальные — устранимыми. Тип конкретной ошибки не зависит от самой ошибки (некорректный ввод, файл не найден, …). Он зависит от решения разработчика: стоит ли продолжать работу программы при этой ошибке, или программа больше ничего не может сделать. Нужно искать компромисс, исходя из требований к надёжности системы и имеющимися ресурсами для разработки, так как восстановление после ошибки требует от разработчика некоторых усилий. В лучшем случае, достаточно просто сообщить о ней пользователю и продолжить работу. Но бывают ситуации, когда для восстановления от ошибки требуется создать целую резервную систему.

Механизм обработки ошибок в Rust требует явно указывать, как вы классифицируете каждую ошибку. Для того чтобы разобраться, как этот механизм устроен, давайте рассмотрим некоторые особенности синтаксиса Rust, которые в нём применяются.

Немного о синтаксисе Rust

Механизм обработки ошибок включает себя две особенности языка Rust: перечисления с данными и трейты.

Трейты

Трейты схожи с концепцией интерфейсов в других языках. Их можно реализовывать на типах, расширяя их функционал. Также, функции могут накладывать ограничение на трейты принимаемых аргументов. Ограничения проверяются при компиляции. Например:

fn main() {
    let int_value: i32 = 42;
    let float_value: f32 = 42.0;

    print_value(int_value);
    // Строка ниже ломает компиляцию, так как для float не реализован трейт Print
    // print_value(float_value);
}

trait Print {
    fn print(&self);
}

impl Print for i32 {
    fn print(&self) {
        println!("Printing i32: {}", self)
    }
}

fn print_value<T: Print>(value: T) {
    value.print()
}

Ссылка на Playground

В данном примере мы определили трейт Print и реализовали его для встроенного целочисленного типа i32. Также, мы определили функцию print_value(), принимающую обобщённый (generic) аргумент value, ограничив варианты его типа только теми, которые реализуют трейт Print. Поэтому в main() мы можем вызвать print_value() только с i32 аргументом.

Более того, при определённых условиях, можно создавать трейт объекты (trait objects). Это динамический объекты, которые могут быть созданы из любого типа, реализующего данный трейт. Конкретная реализация метода трейта выбирается динамически (dynamic dispatch). Например:

trait Animal {
    fn says(&self);
}

struct Cat {}
struct Dog {}

impl Animal for Cat {
    fn says(&self) {
        println!("Meow")
    }
}

impl Animal for Dog {
    fn says(&self) {
        println!("Woof")
    }
}

fn main() {
    let cat = Cat{};
    let dog = Dog{};
    
    say_something(&cat);
    say_something(&dog);
}

fn say_something(animal: &dyn Animal) {
    animal.says()
}

Ссылка на Playground

В данном коде нет необходимости делать функцию say_something() обобщённой, так как конкретная реализация, скрытая за трейт объектом разрешается во время выполнения программы, а не при компиляции.

Также, стоит упомянуть о том, что трейты могут наследоваться. То что трейт Mammal унаследован от трейта Animal означает, что реализовать трейт Mammal может только тип, реализующий Animal.

trait Animal {}

trait Mammal: Animal {}

struct Cat {}
struct Dog {}

impl Animal for Cat {}
impl Mammal for Cat {}

impl Mammal for Dog {}

Ссылка на Playground

Данный код не компилируется, так как мы пытаемся реализовать трейт Mammal на типе Dog, не реализовав Animal, от которого Mammal унаследован.

Перечисления с данными

Данный элемент синтаксиса позволяет привязать данные разных типов к разным вариантам перечисления. Например, вы можете принимать в качестве аргумента IP адрес, не уточняя версию:

enum IpAddr {
    IPv4(u32),
    IPv6(String),
}

fn connect(addr: IpAddr) {
    match addr {
        IpAddr::IPv4(integer_address) => {...}
        IpAddr::IPv6(string_address) => {...}
    }
}

Ключевое слово match позволяет описать действия для различных вариантов перечисления и их содержимого.

Перечисления могут быть обобщенными:

struct DateFormatA {}
struct DateFormatB {}

enum Date<DateFormat> {
    InFormat(DateFormat),
    AsOffset(u32)
}

fn in_format_a() -> Date<DateFormatA> {
    Date::InFormat(DateFormatA {})
}

fn in_format_b() -> Date<DateFormatB> {
    Date::AsOffset(42)
}

fn main() {
    let _a = in_format_a();
    let _b = in_format_b();
}

Ссылка на Playground

Разобравшись с типажами и перечислениями, можно переходить к механизму обработки ошибок.

Обработка ошибок в Rust

В Rust есть два перечисления на которых строится, практически, вся обработка ошибок: Option и Result. Рассмотрим их подробнее.

Option

Определение:

pub enum Option<T> {
    None,
    Some(T),
}

Семантика его проста: либо мы имеем некоторые данные, либо они отсутствуют. Таким образом, возвращая из функции Option мы, тем самым, выражаем мысль, что, возможно, мы не получим ожидаемый результат.

Result

Определение:

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

В отличие от Option, Result позволяет установить не только отсутствие данных, но и причину, в связи с которой они отсутствуют.

Рассмотрим теперь, как в Rust выразить три действия при ошибке, которые мы перечислили в начале статьи:

  • Завершить работу приложения.

  • Обработать ошибку.

  • Пропустить ошибку на более высокий уровень.

Завершаем работу приложения

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

fn main() {
    let broken = true;
    
    if broken {
        panic!("Program is broken!")
    }
}

Ссылка на Playground

Так как для обработки ошибок, обычно, используются Option и Result, для завершения работы программы нужно писать что-то вроде:

match opt {
    Some(value) => value,
    None => panic!("No value in option!")
};

match res {
    Ok(value) => value,
    Err(error) => panic!("Error happaned: {}!", error)
};

Для удобства, Option и Result содержат ассоциированную функцию unwrap(), позволяющую не повторять приведённый выше код. Если перечисление находится в состоянии успеха, то unwrap() достаёт данные из перечисления и позволяет с ними работать. В случае ошибки, unwrap() вызывает панику. У unwrap() есть аналог, позволяющий добавить произвольный текст к выводу: expect().

fn main() {
    let settings = read_settings().unwrap();
    let _service = create_service(settings).expect("Can't create service");
}

fn read_settings() -> Option<String> {
    // Some settings loading code
    None // Settings is not found
}

struct Service {}

#[derive(Debug)] // Generate implementation for Debug trait, required by .expect()
enum CreateServiceError {
    BadSettings,
    InternalError,
}

fn create_service(_settings: String) -> Result<String, CreateServiceError> {
    // Some service creation code
    let bad_settings = true;
    if bad_settings {
        Err(CreateServiceError::BadSettings)
    } else {
        Err(CreateServiceError::InternalError)
    }
} 

Ссылка на Playground

Обрабатываем ошибку

Вызывая функцию, которая может не сработать, мы получаем в качестве результата Option или Result. Если нам известно, что делать в случае неудачи, мы должны выразить свои намерения через конструкции языка. Рассмотрим пример:

fn main() {
    let some_settings = String::from("some settings");

    let s1 = match load_settings() {
        Some(s) => s,
        None => some_settings.clone(),
    };

    let s2 = load_settings().unwrap_or_default();

    let s3 = load_settings().unwrap_or(some_settings);
    
    let s4 = load_settings().unwrap_or_else(|| {String::from("new string")});
    
    println!("s1: {}", s1);
    println!("s2: {}", s2);
    println!("s3: {}", s3);
    println!("s4: {}", s4);
}

fn load_settings() -> Option<String> {
    None
}

Ссылка на Playground

В данном примере мы используем разные способы замены строки настроек, в случае неудачи при её получении:

  • s1 — явно сопоставляем Option с шаблоном и указываем альтернативу.

  • s2 — используем функцию unwrap_or_default(), которая в случае отсутствия данных возвращает значение по умолчанию (пустую строку).

  • s3 — используем unwrap_or(), возвращающую свой аргумент в случае отсутствия данных.

  • s4 — используем unwrap_or_else(), возвращающую результат вызова переданного в неё функтора в случае отсутствия данных. Такой подход позволяет вычислять значение резервного варианта не заранее, а только в случае пустого Option.

Перечисление Result предоставляет аналогичные методы.

Пропускаем ошибку выше

Для начала, сделаем это вручную. Для Option:

fn main() {
    let module = init_module().unwrap();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

fn init_module() -> Option<Module> {
    let settings = match load_settings() {
        Some(s) => s,
        None => return None,
    };

    let dll = match load_dll() {
        Some(dll) => dll,
        None => return None,
    };

    Some(Module { settings, dll })
}

fn load_settings() -> Option<String> {
    None
}

fn load_dll() -> Option<Dll> {
    None
}

Ссылка на Playground

И для Result:

fn main() {
    let module = init_module();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

enum InitModuleError {
    SettingsError(LoadSettingsError),
    DllError(LoadDllError),
}

fn init_module() -> Result<Module, InitModuleError> {
    let settings = match load_settings() {
        Ok(s) => s,
        Err(e) => return Err(InitModuleError::SettingsError(e)),
    };

    let dll = match load_dll() {
        Ok(dll) => dll,
        Err(e) => return Err(InitModuleError::DllError(e)),
    };

    Ok(Module { settings, dll })
}

struct LoadSettingsError {}

fn load_settings() -> Result<String, LoadSettingsError> {
    Err(LoadSettingsError {})
}

struct LoadDllError {}

fn load_dll() -> Result<Dll, LoadDllError> {
    Err(LoadDllError {})
}

Ссылка на Playground

Как видно в примерах, такой подход требует большого количества match конструкций. Это усложняет код, ухудшает его читабельность и добавляет разработчику дополнительной рутинной работы. Во избежание всего этого, создатели языка ввели оператор ?. Расположенный после Option или Result, он заменяет собой match конструкцию. В случае наличия значения, он возвращает его для дальнейшего использования. В случае ошибки, возвращает её из функции. Воспользуемся им в наших примерах. Для Option всё очевидно:

fn main() {
    let module = init_module().unwrap();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

fn init_module() -> Option<Module> {
    let settings = load_settings()?;
    let dll = load_dll()?;
    Some(Module { settings, dll })
}

fn load_settings() -> Option<String> {
    None
}

fn load_dll() -> Option<Dll> {
    None
}

Ссылка на Playground

Для Result всё обстоит немного сложнее. Ведь в случае, если происходит LoadDllError, то компилятору нужно как-то преобразовать её в InitModuleError для возврата из функции. Для этого оператор ? пытается найти способ преобразования для этих ошибок. Для того, чтобы создать такой способ, в стандартной библиотеке существует трейт From. Воспользуемся им:

fn main() {
    let module = init_module();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

enum InitModuleError {
    SettingsError(LoadSettingsError),
    DllError(LoadDllError),
}

impl From<LoadSettingsError> for InitModuleError {
    fn from(e: LoadSettingsError) -> InitModuleError {
        InitModuleError::SettingsError(e)
    }
}

impl From<LoadDllError> for InitModuleError {
    fn from(e: LoadDllError) -> InitModuleError {
        InitModuleError::DllError(e)
    }
}

fn init_module() -> Result<Module, InitModuleError> {
    let settings = load_settings()?;
    let dll = load_dll()?;
    Ok(Module { settings, dll })
}

struct LoadSettingsError {}

fn load_settings() -> Result<String, LoadSettingsError> {
    Err(LoadSettingsError {})
}

struct LoadDllError {}

fn load_dll() -> Result<Dll, LoadDllError> {
    Err(LoadDllError {})
}

Ссылка на Playground

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

Динамические ошибки

В случае, если нет необходимости использовать конкретный тип ошибки, а достаточно просто иметь текстовое сообщение о ней, то можно передавать ошибку в виде трейт объекта std::error::Error, завёрнутого в умный указатель Box (подробнее). Трейт Error определён так:

trait Error: Debug + Display {...}

Как видно из определения, он требует реализации трейтов Debug и Display. Таким образом, Rust вводит требования для всех типов реализующих Error: уметь выводить отладочную и текстовую информацию о себе. Рассмотрим на примере:

use std::fmt;
use std::error::Error;

fn main() {
    init_module().unwrap();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

fn init_module() -> Result<Module, Box<dyn Error>> {
    let settings = load_settings()?;
    let dll = load_dll()?;
    Ok(Module { settings, dll })
}

#[derive(Debug)]
struct LoadSettingsError {}

impl Error for LoadSettingsError {}

impl fmt::Display for LoadSettingsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "load settings error")
    }
}

fn load_settings() -> Result<String, Box<dyn Error>> {
    Err(Box::new(LoadSettingsError {}))
}

#[derive(Debug)]
struct LoadDllError {}

impl Error for LoadDllError {}

impl fmt::Display for LoadDllError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "load dll error")
    }
}

fn load_dll() -> Result<Dll, Box<dyn Error>> {
    Err(Box::new(LoadDllError {}))
}

Ссылка на Playground

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

Полезные библиотеки

Рассмотрим две популярные библиотеки, упрощающие обработку ошибок: thiserror и anyhow.

thiserror

Данная библиотека предоставляет макросы, позволяющие упростить рутинные действия: описание способов конвертации ошибок через From, и реализация трейтов Error и Display. Рассмотрим на примере:

use thiserror::Error; // 1.0.29

fn main() {
    let module = init_module().unwrap();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

#[derive(Debug, Error)]
enum InitModuleError {
    #[error("init module settings error")]
    SettingsError(#[from] LoadSettingsError),
    #[error("init module dll error")]
    DllError(#[from] LoadDllError),
}

fn init_module() -> Result<Module, InitModuleError> {
    let settings = load_settings()?;
    let dll = load_dll()?;
    Ok(Module { settings, dll })
}

#[derive(Debug, Error)]
#[error("load settings error")]
struct LoadSettingsError {}

fn load_settings() -> Result<String, LoadSettingsError> {
    Err(LoadSettingsError {})
}

#[derive(Debug, Error)]
#[error("load dll error")]
struct LoadDllError {}

fn load_dll() -> Result<Dll, LoadDllError> {
    Err(LoadDllError {})
}

Ссылка на Playground

В данном примере, трейт Error реализуется автоматически с помощью макроса #[derive(Error)]. Используя макрос #[error("text to display")] генерируем реализацию трейта Display. Макрос #[from] создаёт реализацию трейта From для конвертации ошибки нижнего уровня в ошибку текущего.

Данные макросы значительно сокращают объём boilerplate кода для обработки ошибок.

anyhow

Данную библиотеку удобно использовать, когда единственное, что интересует нас в ошибке — её текстовое описание. anyhow предоставляет структуру Error. В неё может быть сконвертирован любой объект, реализующий трейт std::Error, что значительно упрощает распространение ошибки по иерархии вызовов. Помимо этого, anyhow::Error позволяет добавлять текстовое описание контекста, в котором произошла ошибка. Эта библиотека сочетается с thiserror. Пример:

use thiserror::Error; // 1.0.29
use anyhow; // 1.0.43;
use anyhow::Context; // 1.0.43;

fn main() {
    let module = init_module().unwrap();
}

struct Module {
    settings: String,
    dll: Dll,
}

struct Dll {}

fn init_module() -> anyhow::Result<Module> {
    let dll = load_dll().context("module initialization")?;
    let settings = load_settings()?;
    Ok(Module { settings, dll })
}

#[derive(Debug, Error)]
#[error("load settings error")]
struct LoadSettingsError {}

fn load_settings() -> Result<String, LoadSettingsError> {
    Err(LoadSettingsError {})
}

fn load_dll() -> anyhow::Result<Dll> {
    anyhow::bail!("load dll error")
}

Ссылка на Playground

Макрос anyhow::bail!() в примере создаёт anyhow::Error с заданным описанием и возвращает её из функции. Псевдоним anyhow::Result определяется так:

type Result<T, E = Error> = Result<T, E>;

Заключение

В начале статьи мы рассмотрели три возможных варианта действий, при получении ошибки: завершить работу программы, обработать ошибку и передать ошибку вверх по иерархии вызовов. Далее, разобравшись с особенностями синтаксиса, мы разобрались на примерах, как выразить наши намерения по отношению к ошибке на языке Rust. Мы увидели, что любой из вариантов поведения должен быть выражен явно. Такой подход повышает надёжность приложения, так как не позволяет разработчику случайно проигнорировать ошибку. С другой стороны, явное описание своих намерений требует дополнительных усилий. Минимизировать эти усилия позволяют библиотеки thiserror и anyhow.

Благодарю за внимание. Поменьше вам ошибок!


Статья написана в преддверии старта курса Rust Developer. Приглашаю всех желающих на бесплатный урок, в рамках которого на примере построения простого веб сервиса рассмотрим популярный веб-фреймворк actix-web в связке с MongoDB + Redis и другие полезные библиотеки для backend разработки.

  • Записаться на бесплатный урок

Понравилась статья? Поделить с друзьями:
  • Runtime error deque mutated during iteration
  • Runtime error running cythonize failed
  • Runtime error cuda out of memory
  • Runtime error roblox
  • Runtime error risen