- All of the codes should follow the Style Guidelines
- All of the warning generated by Clippy should be fixed
Good:
struct QueryId(Uuid);
fn get_query_id() -> QueryId;
Bad:
fn get_query_id(&self) -> Uuid;
-
Just get used to logging exceptions when, and only when, they are handled. If your function is propagating the error upstream (e.g. using the ? operator), it should not log the error. It can, if it makes sense, add more context to it.
-
Signaling Error: Let the bug crash!
All errors should follow the SNAFU crate philosophy and use SNAFU functionality
Good:
- Derives
Snafu
andDebug
functionality - Has a useful, end-user-friendly display message
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display(r#"Conversion needs at least one line of data"#))]
NeedsAtLeastOneLine,
// ...
}
Bad:
pub enum Error {
NeedsAtLeastOneLine,
// ...
}
Good:
- Reads more like an
assert!
- Is more concise
ensure!(!self.schema_sample.is_empty(), NeedsAtLeastOneLine);
Bad:
if self.schema_sample.is_empty() {
return Err(Error::NeedsAtLeastOneLine {});
}
Good:
- Groups related error conditions together most closely with the code that produces them
- Reduces the need to
match
on unrelated errors that would never happen
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Not implemented: {}", operation_name))]
NotImplemented { operation_name: String }
}
// ...
ensure!(foo.is_implemented(), NotImplemented {
operation_name: "foo",
})
Bad:
use crate::errors::NotImplemented;
// ...
ensure!(foo.is_implemented(), NotImplemented {
operation_name: "foo",
})
Good:
- Reduces repetition
pub type Result<T, E = Error> = std::result::Result<T, E>;
...
fn foo() -> Result<bool> { true }
Bad:
...
fn foo() -> Result<bool, Error> { true }
Good:
return NotImplemented {
operation_name: "Parquet format conversion",
}.fail();
Bad:
return Err(Error::NotImplemented {
operation_name: String::from("Parquet format conversion"),
});
Good:
- Reduces boilerplate
input_reader
.read_to_string(&mut buf)
.context(UnableToReadInput {
input_filename,
})?;
Bad:
input_reader
.read_to_string(&mut buf)
.map_err(|e| Error::UnableToReadInput {
name: String::from(input_filename),
source: e,
})?;
Hint for Box<dyn::std::error::Error>
in Snafu:
If your error contains a trait object (e.g. Box<dyn std::error::Error + Send + Sync>
), in order
to use context()
you need to wrap the error in a Box
:
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("gRPC planner got error listing partition keys: {}", source))]
ListingPartitions {
source: Box<dyn std::error::Error + Send + Sync>,
},
}
...
// Wrap error in a box prior to calling context()
database
.partition_keys()
.await
.map_err(|e| Box::new(e) as _)
.context(ListingPartitions)?;
Note the as _
in the map_err
call. Without it, you may get an error such as:
error[E0271]: type mismatch resolving `<ListingPartitions as IntoError<influxrpc::Error>>::Source == Box<<D as Database>::Error>`
--> query/src/frontend/influxrpc.rs:63:14
|
63 | .context(ListingPartitions)?;
| ^^^^^^^ expected trait object `dyn snafu::Error`, found associated type
|
= note: expected struct `Box<(dyn snafu::Error + Send + Sync + 'static)>`
found struct `Box<<D as Database>::Error>`
= help: consider constraining the associated type `<D as Database>::Error` to `(dyn snafu::Error + Send + Sync + 'static)`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
Specific error types are preferred over a generic error with a message
or kind
field.
Good:
- Makes it easier to track down the offending code based on a specific failure
- Reduces the size of the error enum (
String
is 3x 64-bit vs no space) - Makes it easier to remove vestigial errors
- Is more concise
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Error writing remaining lines {}", source))]
UnableToWriteGoodLines { source: IngestError },
#[snafu(display("Error while closing the table writer {}", source))]
UnableToCloseTableWriter { source: IngestError },
}
// ...
write_lines.context(UnableToWriteGoodLines)?;
close_writer.context(UnableToCloseTableWriter)?;
Bad:
pub enum Error {
#[snafu(display("Error {}: {}", message, source))]
WritingError {
source: IngestError,
message: String,
},
}
write_lines.context(WritingError {
message: String::from("Error while writing remaining lines"),
})?;
close_writer.context(WritingError {
message: String::from("Error while closing the table writer"),
})?;
Do not return Result
in test function, let it panic or return Report
instead
Good:
#[test]
fn wtf() -> snafu::Report<Error>{}
Bad:
#[test]
fn wtf() -> Result<(), Error>{}
- When generic is used, write the document about what the generic parameter is.
- Encouraged to consturct a
trait system
before writing code
❗ Async code should never spend a long time without reaching an .await ❗
Using Rustlang's Async Tokio Runtime for CPU-Bound Tasks
According to the website, spawn_blocking
is recommended to use. ❗ but if it is a very hot part of your application, then you should benchmark to compare ❗
- Bug should panic
- If you have to use unwrap/expect, you should guarantee the code never enter the exception code. And do not use unwrap, use expect instead(except test). Message in expect should follow this style.
- Unsafe code should contain comments with
Safety
section - Wrapper of the unsafe code that is safe should check the parameter and return
Result
- Request should ends with Req suffix
- Response should ends with Response suffix
- Service should ends with Service suffix
- Thanks to the influxdb_iox, error handling is forked from them