When I run "cargo clippy -- -W clippy::pedantic" on the code below I get the message:
note: `-W clippy::struct-excessive-bools` implied by `-W clippy::pedantic`
help: consider using a state machine or refactoring bools into two-variant enums
help: for further information visit https://rust-lang.github.io/rust clippy/master/index.html#struct_excessive_bool
I have tried to disable this with #[allow(clippy::clippy::struct-excessive-bools)] but that fails:
5 | #[allow(clippy::clippy::struct-excessive-bools)]
| ^^^^^^ expected identifier, found keyword
Is using #[allow(clippy::clippy::pedantic)] the only solution?
'''
use clap::Parser;
#[derive(Parser, Default, Debug)]
#[clap(version, about)]
#[allow(clippy::clippy::struct-excessive-bools)]
struct Arguments {
#[clap(short, long)]
/// Set aaa
aaa: bool,
#[clap(short, long)]
/// Set bbb
bbb: bool,
#[clap(short, long)]
/// Set ccc
ccc: bool,
#[clap(short, long)]
/// Set ddd
ddd: bool,
}
fn main() {
let args = Arguments::parse();
dbg!(args);
}
'''
The lint is named clippy::struct_excessive_bools, with underscores instead of hyphens; and it doesn't need additional namespacing. #[allow(clippy::struct_excessive_bools)] works - run Clippy with the "Tools" button at the top-right corner with and without the attribute to see.
Related
I'm writing code for a command line tool in Rust. I'm using Crate clap to get and parse command line arguments, I need to get three arguments: --date, --time and --timezone.
For --date and --time I'm using types chrono::NaiveDate and chrono::NaiveTime respectively and it works fine.
src/main.rs
use clap::Parser;
use chrono::prelude::*;
// CLI struct
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] // Read from `Cargo.toml`
pub struct Cli {
/// Select a date in format YYYY-MM-DD.
#[arg(short, long, value_name = "DATE")]
date: Option<NaiveDate>,
/// Clocking time in format HH:MM:SS
#[arg(short, long, value_name = "TIME")]
time: Option<NaiveTime>,
}
fn main() {
let _cli: Cli = Cli::parse();
println!("{:#?}", _cli);
}
Output
$ cargo run -- --date 2021-05-13 --time 08:34:23
Finished dev [unoptimized + debuginfo] target(s) in 2.28s
Running `target/debug/tool --date 2021-05-13 --time '08:34:23'`
Cli {
date: Some(
2021-05-13,
),
time: Some(
08:34:23,
),
}
but when I want add --timezone argument and use it as type chrono::FixedOffset, like this:
[...]
pub struct Cli {
/// Select a date in format YYYY-MM-DD.
#[arg(short, long, value_name = "DATE")]
date: Option<NaiveDate>,
/// Clocking time in format HH:MM:SS
#[arg(short, long, value_name = "TIME")]
time: Option<NaiveTime>,
/// Define local TimeZone
#[arg(long, value_name = "TIMEZONE")]
timezone: Option<FixedOffset>,
}
[...]
Rust compiler shows this error:
$ cargo run -- --date 2021-05-13 --time 08:34:23
Compiling tool v0.1.0 (~/rust/tool)
error[E0599]: the method `value_parser` exists for reference `&&&&&&_AutoValueParser<chrono::FixedOffset>`, but its trait bounds were not satisfied
--> src/main.rs:19:5
|
19 | /// Define local TimeZone
| ^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `&&&&&&_AutoValueParser<chrono::FixedOffset>` due to unsatisfied trait bounds
|
::: ~/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-4.0.18/src/builder/value_parser.rs:2171:1
|
2171 | pub struct _AutoValueParser<T>(std::marker::PhantomData<T>);
| ------------------------------ doesn't satisfy `_: clap::builder::via_prelude::_ValueParserViaParse`
|
::: ~/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.22/src/offset/fixed.rs:27:1
|
27 | pub struct FixedOffset {
| ----------------------
| |
| doesn't satisfy `chrono::FixedOffset: From<&'s std::ffi::OsStr>`
| doesn't satisfy `chrono::FixedOffset: From<&'s str>`
| doesn't satisfy `chrono::FixedOffset: From<OsString>`
| doesn't satisfy `chrono::FixedOffset: From<std::string::String>`
| doesn't satisfy `chrono::FixedOffset: FromStr`
| doesn't satisfy `chrono::FixedOffset: ValueEnum`
| doesn't satisfy `chrono::FixedOffset: ValueParserFactory`
|
= note: the following trait bounds were not satisfied:
`chrono::FixedOffset: ValueEnum`
which is required by `&&&&&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaValueEnum`
`chrono::FixedOffset: ValueParserFactory`
which is required by `&&&&&&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaFactory`
`chrono::FixedOffset: From<OsString>`
which is required by `&&&&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaFromOsString`
`chrono::FixedOffset: From<&'s std::ffi::OsStr>`
which is required by `&&&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaFromOsStr`
`chrono::FixedOffset: From<std::string::String>`
which is required by `&&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaFromString`
`chrono::FixedOffset: From<&'s str>`
which is required by `&_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaFromStr`
`chrono::FixedOffset: FromStr`
which is required by `_AutoValueParser<chrono::FixedOffset>: clap::builder::via_prelude::_ValueParserViaParse`
= note: this error originates in the macro `clap::value_parser` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0599`.
error: could not compile `tool` due to 2 previous errors
Should I use another type for timezone variable?
Should I implement FromStr trait to FixedOffset struct?
I could use timezone variable as Type String, and it works, but I want to use a specific type for TimeZone or Offset, for instance, to save +01:00 value. I will have to manage this param isolated.
I don't want to use chrono::DateTime struct because I will use default values for these arguments, usually, the user only will define --time parameter and I won't force him to write all DateTime.
Thank you
You can't implement FromStr for FixedOffset because you wrote neither the FromStr trait nor the FixedOffset struct and because of the orphan rule. However you will indeed need to write a function that converts a string into a FixedOffset and pass it to clap using the value_parser attribute:
use clap::Parser;
use chrono::prelude::*;
// CLI struct
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] // Read from `Cargo.toml`
pub struct Cli {
/// Select a date in format YYYY-MM-DD.
#[arg(short, long, value_name = "DATE")]
date: Option<NaiveDate>,
/// Clocking time in format HH:MM:SS
#[arg(short, long, value_name = "TIME")]
time: Option<NaiveTime>,
/// Define local TimeZone
#[arg(long, value_name = "TIMEZONE", value_parser = parse_timezone)]
timezone: Option<FixedOffset>,
}
fn parse_timezone (tz: &str) -> Result<FixedOffset, &'static str> {
/// TODO: Parse the string timezone into a `FixedOffset`
Err ("Not implemented")
}
fn main() {
let _cli: Cli = Cli::parse();
println!("{:#?}", _cli);
}
Playground
I want to provide default values for structs to be used only within tests (and not accidentally in production). I thought that I could make the defaults opt-in by defining my own trait TestDefault and implement Default for any type that implements it. Then, one could access all the features of the standard Default trait using something like this
use TestDefault; // TestStruct (defined in my crate) implements TestDefault, thus also Default
let test_struct = TestStruct::default();
To clarify, I want to implement a foreign trait on local type, which should be allowed, but with an artificial layer of indirection to make it opt-in.
I've tried
pub trait TestDefault {
fn test_default() -> Self;
}
impl Default for TestDefault {
fn default() -> Self {
Self::test_default()
}
}
where the compiler complains that error[E0782]: trait objects must include the 'dyn' keyword, inserting it instead causes it to fail because error[E0038]: the trait 'TestDefault' cannot be made into an object.
Then I tried
impl<T> Default for T
where
T: TestDefault,
{
fn default() -> T {
T::test_default()
}
}
and got
error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
--> src/lib.rs:158:14
|
158 | impl<T> Default for T
| ^ type parameter `T` must be used as the type parameter for some local type
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
= note: only traits defined in the current crate can be implemented for a type parameter
which probably hints at the actual error, but I don't entirely understand it. Is there any way to do this? Or get opt-in default some other way?
You can use the #[cfg(test)] annotation to only enable the Default implementation when testing:
struct MyStruct {
data: u32,
}
#[cfg(test)]
impl Default for MyStruct {
fn default() -> Self {
Self { data: 42 }
}
}
fn main() {
let s = MyStruct::default(); // FAILS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let s = MyStruct::default(); // WORKS
assert_eq!(s.data, 42);
}
}
> cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
> cargo run
error[E0599]: no function or associated item named `default` found for struct `MyStruct` in the current scope
--> src/main.rs:13:23
|
1 | struct MyStruct {
| --------------- function or associated item `default` not found for this
...
13 | let s = MyStruct::default(); // FAILS
| ^^^^^^^ function or associated item not found in `MyStruct`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `default`, perhaps you need to implement it:
candidate #1: `Default`
I'm working on a pallet and after switching to the newest node template it stopped building. Here is declaration
#[derive(Encode, Decode, Clone)]
pub struct VendorData<T :Config>
{
pub vendor : VendorId,
pub owner_account_id : T::AccountId,
}
impl<T:Config> Default for VendorData<T> {
fn default() -> Self {
Self {
vendor : Default::default(),
owner_account_id : Default::default(),
}
}
}
#[pallet::storage]
pub(super) type Vendors<T: Config> = StorageMap<_, Blake2_128Concat, VendorId, VendorData<T>, ValueQuery>;
The cargo build error:
error[E0277]: the trait bound `VendorData<T>: TypeInfo` is not satisfied
...
#[pallet::storage]
^^^^^^^ the trait `TypeInfo` is not implemented for `VendorData<T>`
I've tried declaring struct in a different ways, for example by adding TypeInfo:
#[derive(Encode, Decode, Default, Eq, PartialEq, TypeInfo)]
but every time another errors arise, like this:
error: cannot find macro `format` in this scope
--> /home/psu/.cargo/git/checkouts/substrate-7e08433d4c370a21/352c46a/primitives/consensus/aura/src/lib.rs:50:3
|
50 | app_crypto!(ed25519, AURA);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: consider importing this macro:
scale_info::prelude::format
= note: this error originates in the macro `$crate::app_crypto_public_common_if_std` (in Nightly builds, run with -Z macro-backtrace for more info)
There is another storage item which does not cause errors
pub type Cid = Vec<u8>;
#[pallet::storage]
pub(super) type VendorModels<T: Config> = StorageMap<_, Blake2_128Concat, VendorId, Vec<Cid>, ValueQuery>;
So it seems it's only about storing VendorData struct. Please assist, after hours of experiments and googling I'm lost
UPDATE: after adding TypeInfo to all storage structs declaration the pallet is compiled but a lot of errors like below arised:
error: cannot find macro `format` in this scope
error: cannot find macro `vec` in this scope
You will need to either make sure that all your generic types implement TypeInfo or skip the types explicitly.
Based on this example code in Substrate you could write:
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct VendorData<T :Config>
{
pub vendor : VendorId,
pub owner_account_id : T::AccountId,
}
Or alternatively the following should work:
#[derive(Encode, Decode, Clone, TypeInfo)]
pub struct VendorData<AccountId>
{
pub vendor : VendorId,
pub owner_account_id : AccountId,
}
I have a clap struct as follows:
#[derive(Clap)]
#[clap(
version = "0.1.0",
author = "..."
)]
pub struct Cli {
#[clap(long)]
arg_a: Option<String>,
#[clap(long)]
arg_b: Option<String>,
#[clap(long)]
arg_c: Option<String>,
#[clap(long)]
other_arguments: Option<usize>,
}
How do I specify that at least one of {arg_a, arg_b, arg_c} must be present, but also more can be present?
You can read about argument relations at https://github.com/clap-rs/clap/blob/v3.1.14/examples/tutorial_derive/README.md#argument-relations
Add for example:
#[clap(group(
ArgGroup::new("my-group")
.required(true)
.args(&["arg-a", "arg-b", "arg-c"]),
))]
https://docs.rs/clap/latest/clap/struct.ArgGroup.html#method.required "Require an argument from the group to be present when parsing."
Consider this simple crate:
src/lib.rs
mod internal;
use internal::f;
pub(crate) struct Foo {}
pub fn g() {
f();
}
src/internal.rs
use Foo;
pub fn f() -> Foo {
unimplemented!();
}
I want to export only the g function from the crate. I also want to define Foo in lib.rs to simplify reading of my source code. This is an important struct for the crate, so I want to move it to the first
file that any potential reader would open.
The compiler reports:
error[E0446]: private type `Foo` in public interface
--> src/internal.rs:3:1
|
3 | / pub fn f() -> Foo {
4 | | unimplemented!();
5 | | }
| |_^ can't leak private type
How can I fix this issue? Why it is private, while it public for crate?
While it's not hard to catch this case, it would actually be inconsistent for pub to allow private types. pub means that the item may be usable outside the crate. It doesn't matter that you aren't reexporting it, it could be that you reexport it.
The decision is local to the item declaration in order to make it easy to see whether some item could be exposed. If you are in a random file in your project, you can see whether an item is definitely not exposed by either a missing pub specifier or by a constraint on pub. This significantly increases code readability.
For more detailed information, read the relevant RFC or in the tracking issue for the private_in_public lint