Error Handling
Buck2 uses buck2_error replacing both anyhow and thiserror.
Use of anyhow or thiserror in buck2/app is banned except where there are pre-existing
exceptions or when extremely strongly justified.
Result type
fn my_function() -> buck2_error::Result<String> {
// ...
}
Defining custom error types
Use #[derive(Debug, buck2_error::Error)], with an API similar to thiserror. Every error
must carry an ErrorTag:
#[derive(Debug, buck2_error::Error)]
#[error("My error message: {field}")]
#[buck2(tag = Input)]
struct MyError {
field: String,
}
#[derive(Debug, buck2_error::Error)]
#[buck2(tag = Input)]
enum MyErrors {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Missing required field: {0}")]
MissingField(String),
}
Error tags
Tags are defined in app/buck2_data/error.proto. Common generic tags:
Input— user input errors (invalid arguments, malformed build files, ...)Tier0— critical infrastructure failuresEnvironment— external environment issues (system config, external services, network/certs, filesystem)
Using just one of these three tags is fine for most errors. When adding additional tags other than those, reuse existing tags only when appropriate.
Ad-hoc errors
use buck2_error::buck2_error;
if some_condition {
return Err(buck2_error!(
buck2_error::ErrorTag::Input,
"Invalid value: expected {}, got {}",
expected,
actual
));
}
Internal errors (bugs in Buck2 itself)
When reaching a condition that represents an invariant violation and should never fire, panicking
via .expect(), .unwrap(), etc. is ok for file-local invariants. For non-file-local, prefer a
variant of internal error:
use buck2_error::internal_error;
let value = map.get(key).internal_error("Key must exist")?;
return Err(internal_error!(
"Unexpected state: {} should not be empty",
collection_name
));
For Option, prefer .internal_error(...) / .with_internal_error(|| ...)
over .context(...) or .expect(...).
Adding context
buck2_error supports buck_error_context APIs akin to anyhow's context:
use buck2_error::BuckErrorContext;
result.buck_error_context("Failed to process file")?;
result.with_buck_error_context(|| format!("Failed to process file: {}", path))?;
Be somewhat conservative in the use of context, more is not always better.
Conversion
buck2_error::Error impls From for many common error types, including many from std and common
dependencies. When none exists, follow existing patterns, add one if semantically appropriate, and
otherwise use an ad-hoc conversion:
use buck2_error::conversion::from_any_with_tag;
some_result.map_err(|e| from_any_with_tag(e, ErrorTag::Tier0))?;
Worked example
fn process_artifact(&self, artifact: &Artifact) -> buck2_error::Result<()> {
let path = artifact.path()
.buck_error_context("Failed to get artifact path")?;
if !path.exists() {
return Err(buck2_error!(
buck2_error::ErrorTag::Input,
"Artifact does not exist: {}",
path
));
}
Ok(())
}