Error handling in embedded Rust
Since the logic of the EP0SETUP
event handling is getting more complex with each added event, you can see that usb-4.rs
was refactored to add error handling: the event handling now happens in a separate function that returns a Result
. When it encounters an invalid host request, it returns the Err
variant which can be handled by stalling the endpoint:
#![allow(unused)] fn main() { fn on_event(/* parameters */) { match event { /* ... */ Event::UsbEp0Setup => { if ep0setup(/* arguments */).is_err() { // unsupported or invalid request: // TODO add code to stall the endpoint defmt::warn!("EP0IN: unexpected request; stalling the endpoint"); } } } } fn ep0setup(/* parameters */) -> Result<(), ()> { let req = Request::parse(/* arguments_*/)?; // ^ early returns an `Err` if it occurs // TODO respond to the `req`; return `Err` if the request was invalid in this state Ok(()) } }
Note that there's a difference between the error handling done here and the error handling commonly done in std
programs. std
programs usually bubble up errors to the top main
function (using the ?
operator), report the error (or chain of errors) and then exit the application with a non-zero exit code. This approach is usually not appropriate for embedded programs as
(1) main
cannot return,
(2) there may not be a console to print the error to and/or
(3) stopping the program, and e.g. requiring the user to reset it to make it work again, may not be desirable behavior.
For these reasons in embedded software errors tend to be handled as early as possible rather than propagated all the way up.
This does not preclude error reporting. The above snippet includes error reporting in the form of a defmt::warn!
statement. This log statement may not be included in the final release of the program as it may not be useful, or even visible, to an end user but it is useful during development.