shepmaster / snafu

Easily assign underlying errors into domain-specific errors while adding context
https://docs.rs/snafu/
Apache License 2.0
1.4k stars 60 forks source link

Document performance considerations of context vs with_context #318

Open TheButlah opened 2 years ago

TheButlah commented 2 years ago

It would be good to document best practices for whether we should use context() or with_context(). Is it better to eagerly evaluate the error struct or lazily do it? Should I always just use with_context()?

I'm using this in a no_std environment so performance matters to me.

shepmaster commented 2 years ago

In general, there should be no inherent performance differences between context or with_context. Rust's optimizers pretty trivially improve the closure for with_context.

The important thing to remember is that, most of the time, you shouldn't have any function calls when using context:

// Good
foo.context(BarSnafu { name, id: 32, date: &date });

// Bad
foo.context(BarSnafu { name: name.to_string(), id: fetch_id_from_server(), date: Date::now() });

// Better
foo.with_context(|| BarSnafu { name: name.to_string(), id: fetch_id_from_server(), date: Date::now() });

The rationale is that those function calls will be evaluated regardless if foo is in an error state or not. When using with_context, the closure is only evaluated when foo is a Result::Err, so the function calls are made lazy again.

However, most of the time, you can use context because SNAFU will only apply the Into::into conversion on the context arguments when the value is an Err. Most of the time, this means you should transfer ownership of an existing value to the context selector or give it a reference (see name and date in the first example).