Using a structure as a command line argument in clap - rust

Trying to use a struct within a struct in clap:
use clap::{Args, Parser};
use std::path::PathBuf;
#[derive(Parser, Debug)]
enum Command {
Foo(Foo),
}
#[derive(Args, Debug)]
struct Foo {
bar: Option<Bar>,
path: PathBuf,
}
#[derive(Parser, Clone, Debug)]
struct Bar {
bla: u8,
bla_2: String,
}
fn main() {
let cli = Command::parse();
println!("cli {:#?}", cli);
}
So I could call the app with the following options: cargo run -- foo bar 42 baz /tmp/a or just cargo run -- foo /tmp/a since the bar argument is optional.
However, currently it does not build:
--> src/main.rs:11:5
|
11 | bar: Option<Bar>,
| ^^^ the trait `FromStr` is not implemented for `Bar`
|
And since the values within Bar have to be space-separated implementing a FromStr would not do the trick anyway.
Is it even possible to do something of this fashion in clap currently?

There are several problems with your code. The biggest one is:
An optional positional item can never come before a required positional argument
This is a problem in your case because your command line looks like this:
cargo run -- <required> [optional] /tmp/a
If you have a required path at the end, there can not be an optional positional argument before that.
Further problems:
#[derive(Parser)] should be attached to a struct, not an enum.
There should only be one #[derive(Parser)], which represents the entry object of your arguments parser.
I'm unsure how else to help you, except pointing out your problems. If the invocations cargo run -- foo bar 42 baz /tmp/a and cargo run -- foo /tmp/a are non-negotiable, I don't think clap is the right library for you; I think you should parse by hand.

Related

How to use clap to take create a vector

I have a basic struct like this
pub struct Args {
#[clap(short, long, value_parser)]
pub files: Vec<String>,
}
I'm trying to get this struct to take multiple values like such
cargo run -- --files hello world
But when I run this, it doesn't see world correctly. It errors with this:
error: Found argument 'world' which wasn't expected, or isn't valid in this context
What is the proper way to have clap populate this struct?
You can use num_args to specify a range for the number of argument occurences and values allowed like so:
use clap::Parser;
#[derive(Parser)]
pub struct Args {
#[clap(short, long, value_parser, num_args = 1.., value_delimiter = ' ')]
pub files: Vec<String>,
}
fn main() {
let args = Args::parse();
println!("files: {:?}", args.files);
}
This will allow both
cargo run -- --files hello world
and
cargo run -- --files hello --files world
Specifying the value_delimiter is not strictly neccessary here, but I just wanted to point out that you can use different characters as delimiters like this.
If you also want to allow empty arrays beeing passed, you can change the num_args attribute like so num_args = 0...

Missing Copy/Debug traits warnings when defining enum with `strum` traits in macro

I want to define an enum using a macro. The enum needs to implement the strum traits {Display, EnumIter, EnumString}. I also want to keep the warnings missing_copy_implementations, missing_debug_implementations on. I came up with the following snippet:
#![warn(
missing_copy_implementations,
missing_debug_implementations,
)]
macro_rules! define_fruits {
{$($fruit:ident -> $name:literal),* $(,)?} => {
#[derive(Display, EnumIter, EnumString, Clone, Copy, Debug)]
pub enum Fruits {
$(
#[strum(to_string = $name)]
$fruit,
)*
}
};
}
define_fruits! {
Apple -> "green",
Orange -> "orange",
}
The above works fine except I get the warnings:
|
4 | missing_copy_implementations,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this warning originates in the macro `define_fruits` (in Nightly builds, run with -Z macro-backtrace for more info)
and similar for missing_debug_implementations.
These warnings go away when I remove Display, EnumIter, EnumString from my enum. They also go away if I define the same enum outside of a macro.
I cannot seem to find a way to get rid of the warnings in the above scenario, can someone help?
TL/DR: the problem is with derive(EnumIter), which isn't compatible with these lints. I've opened the issue on strum repository asking for possible changes.
To see what really happens, let's try to cargo expand the current code. After some simplification and stubbing out the unnecessary parts, we can see the following structure:
///An iterator over the variants of [Self]
pub struct FruitsIter {
idx: usize,
back_idx: usize,
marker: PhantomData<()>,
}
This is a type you get when you call IntoEnumIterator::iter derived for your enum. There's no Copy or Debug implementation for this struct - they are neither derived nor explicitly added by strum, so the lint sees the violation and fires.
A hint on the problem source can be seen when looking at the exact error, as provided by cargo check (or at the eror highlighting from rust-analyzer):
warning: type could implement `Copy`; consider adding `impl Copy`
--> src/lib.rs:10:27
|
10 | #[derive(Display, EnumIter, EnumString, Clone, Copy, Debug)]
| ___________________________^
11 | | pub enum Fruits {
| |___________^
Note that the span of the "erroneous" code starts from the EnumIter - since that's the token which the FruitsIter's span is tied to, warning on the FruitsIter is shown as a warning on the derive(EnumIter). And indeed, if we drop this derive - warning disappears.
There's nothing you can do with this, I'm afraid, aside from explicitly allowing these lints for the whole module containing your enum. This is something that should probably be fixed by the strum maintainers.

how to make external recursive type insertable to a HashSet

I use https://github.com/dtolnay/clang-ast to parse the JSON produced by Clang representing an AST to be available as a Rust data type, specifically the Node. I'd like to insert the nodes from the tree (Node<T> is recursive structure) into a HashSet. I could not even insert the root note:
use std::collections::HashSet;
use log::debug;
use std::env;
use serde::Deserialize;
pub type Node = clang_ast::Node<Clang>;
#[derive(Deserialize)]
#[derive(Debug)]
pub enum Clang {
BinaryOperator(BinaryOperator),
Other,
}
#[derive(Deserialize, Debug)]
pub struct BinaryOperator {
pub opcode: String,
pub range: clang_ast::SourceRange,
}
fn main() {
env_logger::init();
let json = std::fs::read_to_string("ast.json").unwrap();
let node :Node = serde_json::from_str(&json).unwrap();
let mut node_set = HashSet::new();
node_set.insert(node);
}
this fails the compilation with:
error[E0277]: the trait bound `clang_ast::Node<Clang>: Eq` is not satisfied
--> src/main.rs:28:21
|
28 | node_set.insert(node);
| ------ ^^^^ the trait `Eq` is not implemented for `clang_ast::Node<Clang>`
| |
| required by a bound introduced by this call
|
note: required by a bound in `HashSet::<T, S>::insert`
...
So, I tried to add the Eq and PartialEq implementations using (following some advice from How to implement Eq and Hash for my own structs to use them as a HashMap key?):
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Node {}
however this fails with:
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src/main.rs:8:1
|
8 | impl PartialEq for Node {
| ^^^^^---------^^^^^----
| | | |
| | | `clang_ast::Node` is not defined in the current crate
| | `clang_ast::Node` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
How do I make this work ? Also, why does the language/compiler imposes such limit ?
Following the answers on Implement foreign trait for foreign type does not really help because there are more pieces to the puzzle (implementing Eq, PartialEq, the Hash trait, dealing with the recursive nature of clang_ast::Note<T>).
How do I make this work ?
Define a newtype wrapper e.g. struct Node(clang:ast::Node<Clang>).
, or implement the trait on clang_ast::Node<Clang> rather than just clang_ast::Node1.
Also, why does the language/compiler imposes such limit ?
Coherence. There should be only one implementation of a trait for a struct (ignoring specialisation). Allowing crates to implement traits they didn't define on types they didn't define is an opportunity for conflicts and incoherences (where one dependency uses one implementation and an other library uses an other incompatible implementation) which could lead to unsafety if they interact with the same objects in different ways (e.g. a generic collection of some sort, like a hashset).
Here though per the implementation coherence rules since Clang is a local type as long as there are no uncovered types appearing as one of its parameters it should be fine (I could not test this on the playground as clang_ast is to minor to figure there, but it works fine with Box).
In other contexts (like Box) overlapping can also be an issue, but here clang_ast::Node does not implement PartialEq at all, not even conditionally. Until that happens there's no issue.
[1]: incidentally you may want to report that the error message is misleading, since Node is a generic type I don't think it makes sense that Rust accepts this attempt. In fact I can not reproduce it, on the playground trying to impl PartialEq for Box fails with "error[E0107]: missing generics for struct Box".
edit: struct out the completely incorrect bits where I thought the coherence rules were less strict than they actually are (which is only the case for Box, Pin, and reference types).

Why does Rust not allow the copy and drop traits on one type?

From the book:
Rust won’t let us annotate a type with the Copy trait if the type, or any of its parts, has implemented the Drop trait. If the type needs something special to happen when the value goes out of scope and we add the Copy annotation to that type, we’ll get a compile time error.
Why was the design decision made to disallow Copy and Drop on the same type?
The Drop trait is used in an RAII context, typically when some resource needs to be released/closed when the object is destroyed.
In the other hand, a Copy type is a trivial type that can be copied with a memcpy only.
With those two descriptions, it is clearer that they are exclusive: it makes no sense to memcpy nontrivial data: what if we copy the data, and we drop one of the copies? The inner resource of the other copy will not be reliable anymore.
In fact, Copy in not even a "real" trait, in that it does not define any function. It is a special marker that says to the compiler: "you can duplicate myself with a simple bytes copy". So you cannot provide a custom implementation of Copy, because there is no implementation at all. However, you can mark a type as copyable:
impl Copy for Foo {}
or better, with a derive:
#[derive(Clone, Copy)]
struct Foo { /* ... */ }
This builds only if all the fields implement Copy. Otherwise, the compiler refuses to compile because this is unsafe.
For the sake of an example, let's suppose that the File struct implements Copy. Of course, this is not the case, and this example is wrong and cannot compile:
fn drop_copy_type<T>(T x)
where
T: Copy + Drop,
{
// The inner file descriptor is closed there:
std::mem::drop(x);
}
fn main() {
let mut file = File::open("foo.txt").unwrap();
drop_copy_type(file);
let mut contents = String::new();
// Oops, this is unsafe!
// We try to read an already closed file descriptor:
file.read_to_string(&mut contents).unwrap();
}
The other answers here are talking about why we don't usually want to implement both Copy and Drop for the same type, but that's not the same as explaining why it's forbidden. It might seem like a toy example like this should work just fine:
#[derive(Copy, Clone)]
struct Foo {
i: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
// No problematic memory management here. Just print.
println!("{}", self.i);
}
}
fn main() {
let foo1 = Foo { i: 42 };
let foo2 = foo1;
// Shouldn't this just print 42 twice?
}
But indeed, if we try to compile that (using Rust 1.52), it fails as expected:
error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor
--> src/main.rs:1:10
|
1 | #[derive(Copy, Clone)]
| ^^^^ Copy not allowed on types with destructors
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0184`.
See the "For more information" note at the bottom? Those are often helpful. Let's run rustc --explain E0184:
The `Copy` trait was implemented on a type with a `Drop` implementation.
Erroneous code example:
```
#[derive(Copy)]
struct Foo; // error!
impl Drop for Foo {
fn drop(&mut self) {
}
}
```
Explicitly implementing both `Drop` and `Copy` trait on a type is currently
disallowed. This feature can make some sense in theory, but the current
implementation is incorrect and can lead to memory unsafety (see
[issue #20126][iss20126]), so it has been disabled for now.
[iss20126]: https://github.com/rust-lang/rust/issues/20126
Following that issue link leads to a discussion of "zeroing-on-drop". Present-day Rust doesn't do this anymore, but up until around 2016 Rust implemented "dynamic drop" by zeroing all the bits of a value when dropping it. But of course that isn't a valid implementation if a type can be both Copy and Drop -- Rust can't zero out a value that you're allowed to keep using -- so implementing both of those traits on the same type was disallowed. The discussion ends with this interesting comment:
Anyhow, it's easiest to forbid it for now. We can always make it legal later if someone comes up with a persuasive use case. Idempotent destructors seem like a bit of an odd thing.
What's above is the explanation for Rust's current behavior, as best I can tell. But I think there's another reason to keep things the way they are, which I haven't seen discussed: Copy currently implies that a value can be both bitwise copied and also bitwise overwritten. Consider this code:
#[derive(Copy, Clone)]
struct Foo {
i: i32,
}
fn main() {
let mut ten_foos = [Foo { i: 42 }; 10];
let ten_more_foos = [Foo { i: 99 }; 10];
// Overwrite all the bytes of the first array with those of the second.
unsafe {
std::ptr::copy_nonoverlapping(&ten_more_foos, &mut ten_foos, 1);
}
}
This unsafe code is totally fine today. In fact, [T]::copy_from_slice will do exactly the same thing for any T: Copy. But would it still be ok, if Foo (or any other Copy type) were allowed to be Drop? Our code here, and the standard library code in copy_from_slice, would be destroying objects without dropping them!
Now, technically, failing to call the destructor of an object is allowed. There was a very interesting discussion back in the day that led to std::mem::forget going from unsafe to safe shortly before Rust 1.0. So it's possible that Rust could allow Copy + Drop without leading to any undefined behavior, despite this issue. But it would be quite surprising that certain (standard!) functions would fail to call the destructors you expect. The property that "Copy objects can be bitwise copied and bitwise overwritten" seems like a good one to keep.
Quoting the documentation.
[...] [A]ny type implementing Drop can't be Copy, because it's managing some resource besides its own size_of::<T> bytes.

What is imported when I use a struct from another module?

I have the module mod1.rs:
pub struct Foo;
impl Foo {}
impl Drop for Foo {
fn drop(&mut self) {}
}
In file2.rs I wrote use mod1::Foo;.
What do I actually have in file2.rs? Only struct Foo, impl Foo? What about impl Drop for Foo?
If I get all traits for Foo in file2.rs, and I write
fn my_func(foo: Foo)..., what do I have here? Is Foo a struct or a trait (impl Foo) here?
I read the Rust book and manual, but they explain only
explicit usage, not mention what happens with trait with the same name (impl). The Rust book tells you to import traits explicitly, if so and Drop is not imported by use mod1::Foo, this is a really, really bad thing.
In file2.rs I wrote use mod1::Foo;.
What do I actually have in file2.rs? Only struct Foo, impl Foo? What about impl Drop for Foo?
When you use a type like a struct or an enum, you get all of the inherent methods; those defined in the impl Foo. You'd also be able to access any public fields on the type.
If I get all traits for Foo in file2.rs, and I write fn my_func(foo: Foo), what do I have here? Is Foo a struct or a trait (impl Foo) here?
impl Foo is not a trait. trait Bar defines a trait. impl Bar for Foo implements a trait for the type Foo. impl Foo creates inherent methods; these are not related to traits.
I read the Rust book and manual, but they explain only explicit usage, not mention what happens with trait with the same name (impl). The Rust book tells you to import traits explicitly, if so and Drop is not imported by use mod1::Foo, this is a really, really bad thing.
That would be a very bad idea for the language designers to have made. Thankfully, they didn't do that. Importing something simply allows the code that imported it to use it. It doesn't cause the code to disappear if it's not imported.
The compiler itself is the user of types that implement Drop, so you can think of it as the compiler implementation has use Drop in it somewhere. This is probably not literally true, but a mental model. Just because your code doesn't import Drop doesn't mean some other code couldn't.
As mentioned elsewhere, you don't have to import Drop anyway, as it's included in the prelude.

Resources