Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: more Results, fewer panics, always have backtraces #761

Merged
merged 54 commits into from
Sep 10, 2024
Merged

Conversation

lwwmanning
Copy link
Member

@lwwmanning lwwmanning commented Sep 6, 2024

fixes #564

The goals of this PR is to ensure the following:

  • reasonable developer ergonomics / not too verbose
  • return a VortexResult with a well-formed error from most fallible functions
  • panics are allowed in cases of programmer error / invariants being violated (i.e., panics should correspond logically to places where we might write an assert)
  • ensure a good error message and a backtrace in case of panic

To accomplish this, this PR does several things.

First, it adds much stricter clippy lints (e.g., deny on unwrap, expect, panic; forbids fallible From impls; etc.).

Second, I did an audit to make functions that are truly fallible return VortexResult instead of panicking. The remaining ones that I saw but didn't fix are tracked under #765

Third, for places where we truly want to panic (specifically because an internal invariant has been violated, typically indicating programmer error), this PR adds:

  • a new macro (vortex_panic!)
  • VortexUnwrap trait, which is implemented only on VortexResult and adds a vortex_unwrap() function
  • VortexExpect trait, which is implemented on VortexResult and Option<T> and adds a vortex_expect function that takes a string literal

Taken together, the main downsides are that we have a special snowflake unwrap/expect instead of the traditional ones, and we don't have an effective lint rule to prevent calling those in functions that return Result... but I think the latter at least is acceptable (I know @AdamGS finds those lints overly restrictive anyway).

ALTERNATIVELY, a middle ground would be to replace calls to vortex_unwrap and vortex_expect with expect and un-deny expect_used. This would be more "typical" Rust but with the disadvantage that we wouldn't get backtraces by default in the cases where we have bugs. Or we could do unwrap_or_else(|err| vortex_panic!(err)) or ok_or_else(|| vortex_panic!(...)) everywhere, but that's pretty verbose.

@lwwmanning lwwmanning changed the title better error handling feat: more Results, fewer panics, always have backtraces Sep 9, 2024
Cargo.toml Show resolved Hide resolved
@lwwmanning lwwmanning marked this pull request as ready for review September 9, 2024 20:39
@lwwmanning lwwmanning enabled auto-merge (squash) September 9, 2024 21:09
@gatesn
Copy link
Contributor

gatesn commented Sep 10, 2024

What do you mean we wouldn’t get backtraces by default using builtin expect?

Copy link
Contributor

@AdamGS AdamGS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, seems to me like most rust projects end up having something like this and I think it solves most things that bothered me with the current clippy config while still maintaining error context and backtraces

Copy link
Member

@robert3005 robert3005 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments about lazy unwraps. I am not convinced this is the best way of doing things but don't know how to do anything better

@@ -190,7 +190,8 @@ pub fn compress_taxi_data() -> Array {
let chunks = reader
.into_iter()
.map(|batch_result| batch_result.unwrap())
.map(Array::from)
.map(Array::try_from)
.map(Result::unwrap)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should collect errors and unwrap at the ends

@@ -45,7 +45,7 @@ pub fn data_vortex_uncompressed(fname_out: &str, downloaded_data: PathBuf) -> Pa
let array = ChunkedArray::try_new(
reader
.into_iter()
.map(|batch_result| Array::from(batch_result.unwrap()))
.map(|batch_result| Array::try_from(batch_result.unwrap()).unwrap())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collect errors and unwrap at the end

@@ -101,7 +102,7 @@ pub fn compress_parquet_to_vortex(parquet_path: &Path) -> VortexResult<ChunkedAr
let chunks = reader
.map(|batch_result| batch_result.unwrap())
.map(|record_batch| {
let vortex_array = Array::from(record_batch);
let vortex_array = Array::try_from(record_batch).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should collect errors and unwrap at the end

.map(Array::from)
.map(|a| a.into_struct().unwrap())
.map(Array::try_from)
.map(|a| a.unwrap().into_struct().unwrap())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should collect errors

@lwwmanning
Copy link
Member Author

What do you mean we wouldn’t get backtraces by default using builtin expect?

We're a library. Unless the actual binary sets RUST_BACKTRACE=1, regular expect would just show us something like:

thread 'main' panicked at 'here's the message I gave to expect', foo.rs:100:10
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Which is just not that helpful

@lwwmanning lwwmanning merged commit 2c3da0c into develop Sep 10, 2024
4 checks passed
@lwwmanning lwwmanning deleted the wm/clippy branch September 10, 2024 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enforce no unwrap()
4 participants