How to make all arguments have the same number of values? - rust

I'm building a CLI that will be used like this:
$ my_cli command aa aa -- bb bb
Arguments aa aa and bb bb will be collected into separated Vec<String>s. Let's call them vec_a and vec_b. I need both vectors to be the same length. This is what I tried in order to verify that the user gives the same number of values for both arguments:
use clap::{Parser, Subcommand};
// --snip--
#[derive(Parser)]
#[clap(author, version, about)]
pub struct Cli {
/// Operation to perform
#[clap(subcommand)]
pub command: Subcommands,
}
#[derive(Subcommand)]
pub enum Subcommands {
// --snip--
/// does something with vec_a and vec_b. Both must have the same length.
Rebuild {
/// Reads vec_a
#[clap(value_parser, required(true))]
vec_a: Vec<String>,
/// Reads vec_b
#[clap(
value_parser,
required(true),
number_of_values=vec_a.len())]
vec_b: Vec<String>,
},
}
// --snip--
I get this error:
~/my_cli$ cargo run -- rebuild aa aa -- bb bb
error[E0425]: cannot find value `vec_a` in this scope
|
| number_of_values=vec_a.len())]
| ^^^^^ not found in this scope
So, how do I check that both arguments have the same length?
Maybe this could be expanded to a more general question: How do I read an argument from the derive properties of another argument?

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...

How to create my own type wrapping the array type, in order to avoid the Copy trait?

You know how you can create a new type by wrapping an existing type just because you want to avoid the Copy trait?! For example, you have bool and you want a new type MyBool, you do struct MyBool(bool); and then you can use that to avoid the Copy trait.
What I want to know is how do you do it(create a new type, that is) for an array type? eg. for the type of a in let a = [0; 4]; which is [{integer}; 4], array of four integer elements. And can you do it for an array of a specific type X and of a specific len Y ? or, only for an array of type T (think generics) and with/without(?) a specific array length embedded in the new type ?
So, for bool, the following code doesn't show any warnings/errors(even if you run cargo clippy on it):
#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]
#![allow(clippy::blanket_clippy_restriction_lints)] //workaround clippy
// might want to deny later:
//#![allow(clippy::default_numeric_fallback)] // might want to deny later!
//#![allow(clippy::dbg_macro)]
fn main() {
let mut has_spawned:bool=false;
//...
let handler=std::thread::spawn(move || {
println!("Before {has_spawned}!"); // false
has_spawned=true;
println!("Set {has_spawned}!"); // true
});
#[allow(clippy::unwrap_used)]
handler.join().unwrap();
println!("Current {has_spawned}!"); // false
}
But I want it to show me errors, thus, I use a new type for bool, MyBool:
#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]
#![allow(clippy::blanket_clippy_restriction_lints)] //workaround clippy
#[derive(Debug)]
struct MyBool(bool);
fn main() {
let mut my_has_spawned:MyBool=MyBool(false);
//...
let handler=std::thread::spawn(move || {
println!("Before {my_has_spawned:?}!"); //MyBool(false)
//my_has_spawned=MyBool(true);
my_has_spawned.0=true;
println!("Set {my_has_spawned:?}!"); // MyBool(true)
});
#[allow(clippy::unwrap_used)]
handler.join().unwrap();
println!("Current {my_has_spawned:#?}!"); // value borrowed here after move, XXX: this is what
// I wanted!
}
and now it shows me exactly what I want to see:
error[E0382]: borrow of moved value: `my_has_spawned`
--> /home/user/sandbox/rust/copy_trait/gotcha1/copy_trait_thread_newtype/src/main.rs:20:24
|
10 | let mut my_has_spawned:MyBool=MyBool(false);
| ------------------ move occurs because `my_has_spawned` has type `MyBool`, which does not implement the `Copy` trait
11 | //...
12 | let handler=std::thread::spawn(move || {
| ------- value moved into closure here
13 | println!("Before {my_has_spawned:?}!"); //MyBool(false)
| -------------- variable moved due to use in closure
...
20 | println!("Current {my_has_spawned:#?}!"); // value borrowed here after move, XXX: this is what
| -------------------^^^^^^^^^^^^^^-------
| | |
| | value borrowed here after move
| in this macro invocation (#1)
|
::: /usr/lib/rust/1.64.0/lib/rustlib/src/rust/library/std/src/macros.rs:101:1
|
101 | macro_rules! println {
| -------------------- in this expansion of `println!` (#1)
...
106 | $crate::io::_print($crate::format_args_nl!($($arg)*));
| --------------------------------- in this macro invocation (#2)
|
::: /usr/lib/rust/1.64.0/lib/rustlib/src/rust/library/core/src/macros/mod.rs:906:5
|
906 | macro_rules! format_args_nl {
| --------------------------- in this expansion of `$crate::format_args_nl!` (#2)
For more information about this error, try `rustc --explain E0382`.
error: could not compile `copy_trait_thread_newtype` due to previous error
Similarly to the above, I want to use the new type for this array program that yields no warnings or errors(even through cargo clippy):
#![deny(clippy::all, clippy::pedantic, clippy::nursery, warnings, future_incompatible,
nonstandard_style, non_ascii_idents, clippy::restriction, rust_2018_compatibility,
rust_2021_compatibility, unused)]
#![allow(clippy::print_stdout, clippy::use_debug, clippy::missing_docs_in_private_items)]
#![allow(clippy::blanket_clippy_restriction_lints)] //workaround clippy
// might want to deny later:
#![allow(clippy::default_numeric_fallback)] // might want to deny later!
#![allow(clippy::dbg_macro)]
//src: https://users.rust-lang.org/t/rust-book-suggestion-add-a-section-regarding-copy-vs-move/1549/2
fn foo(mut x: [i32; 4]) {
println!("x(before) = {:?}", x);
x = [1, 2, 3, 4];
println!("x(after) = {:?}", x);
}
//src: https://stackoverflow.com/a/58119924/19999437
fn print_type_of<T>(_: &T) {
//println!("{}", std::any::type_name::<T>());
println!("{}", core::any::type_name::<T>());
}
//struct MyArray(array); //TODO: how?
fn main() {
let a = [0; 4];
//a.something();//method not found in `[{integer}; 4]`
//a=1;//so this is an array
//dbg!(a);
println!("{:#?}", print_type_of(&a)); // i32
foo(a); //sneakily copied! thanks Copy trait!
println!("a = {:?}", a);//unchanged, doh! but since it was just copied above, can use it here
//without errors!
}
output of that is:
[i32; 4]
()
x(before) = [0, 0, 0, 0]
x(after) = [1, 2, 3, 4]
a = [0, 0, 0, 0]
So you see, I want a new type in order to avoid the copying of a when foo(a); is called, so that the computer/compiler can keep track of when I would introduce such easy bugs in my code, by error-ing instead, which happens only when a is moved(instead of just copied) when foo(a); is called.
Side note: do you think that this clippy lint https://github.com/rust-lang/rust-clippy/issues/9061 if implemented would be able to warn/err for me in such cases? That would be cool!
Personally I dislike the Copy trait for the only reason that it bypasses the borrow checker and thus allows you to write code that introduces subtle bugs. ie. you can keep using the stale value of the "moved"(copied) variable and the compiler won't complain.
Even if there are better ways to do what I want, please also do answer the title question: how I can wrap the array type in my own new type?
You can wrap an array the same way you would wrap a bool:
struct MyArray ([i32; 4]);
And you can use generics if you don't want to redefine different wrapper types for each array:
struct MyArray<T, const N: usize> ([T; N]);
I don't understand why you don't want your variable to be copied. Your variable being copied isn't a bug or won't create bugs.
point 1
The only way in rust to modify data via a function is either :
to pass a mutable reference of your variable to a function
to assign the result of the function back to your original mutable value
In the example you show, your variable isn't even defined as mutable in the first place, there is no way it would change.
point 2
The Copy trait doesn't bypass the borrow checker, as the borrow checker only goal is :
to make sure a pointer always points to valid data
to make sure there is always only one owner of the data
In the case you show, you don't involve any pointer, and the data itself is duplicated, so the borrow checker won't give a crap, it isn't even involved. Whatever happens in the function stays in the function.
anyway
Now if you want, for whatever reason, the borrow checker to show you errors in this situation (once again, there is no reason for the borrow checker to be involved), the proper way would probably be to give the ownership of your variable to a dedicated type like a Box, and use the box for your operations (box don't implement the Copy trait).
The following code involving a bool works
fn main() {
let a = false;
effect(a);
println!("main function : {a}");
}
fn effect(mut a: bool) {
println!("effect before : {a}");
a = !a;
println!("effect after : {a}");
}
The following code involving a bool wrapped inside a box doesn't work
`fn main() {
let a = Box::from(false);
effect(a);
println!("main function : {a}"); // error
}
fn effect(mut a: Box<bool>) {
println!("effect before : {a}");
*a = !*a;
println!("effect after : {a}");
}
Now, once again, I don't understand why anyone would want to do this. This change has a performance impact, as you now need to take your variable from the heap before any operation, instead if just reading it from the stack.

Using a structure as a command line argument in clap

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.

Rust rusqlite cannot insert row

The following code fails to compile with:
55 | (":dataset_id", &dataset_id),
| ^^^^^^^^^^^ expected `u32`, found `i32`
pub fn save(&mut self, annotations: Vec<Record>, dataset_id: i32) -> Result<(), Error> {
let mut tx = self.conn.transaction()?;
for record in records {
let json: String = record();
let sql: &str =
"INSERT INTO records (record_id, dataset_id, value)
VALUES (:record_id, :dataset_id, :value)";
let mut statement = tx.prepare(sql)?;
statement.execute(&[
(":record_id", &record.id),
(":dataset_id", &dataset_id),
(":value", "hello world")]);
};
tx.commit()?;
Ok(())
}
And if I remove dataset_id from my SQL statement and comment out the line: (":dataset_id", &dataset_id),
Then it fails to compile with:
56 | (":value", &"hello".to_string()),
| ^^^^^^^^^^^^^^^^^^^^ expected `u32`, found struct `std::string::String`
The argument to execute is a P: Params, i.e. "any type that implements Params". The Rust compiler will not make a guess at what specific type you want and then process the argument accordingly. Instead, it will just resolve the type of the argument on its own and then see if it implements Params.
This is your argument:
&[(":record_id", &record.id),
(":dataset_id", &dataset_id),
(":value", "hello world")]
On its own, what is the type? It's a reference to an array literal containing three tuples: a (&str, &u32), a (&str, &i32), and a (&str, &str).
Lacking any further information, the compiler guesses that the first element is the canonical one, and thus tries to convert the others accordingly. Thus you get the "cannot convert &i32 to &u32" errors.
What you need, however, is an array of (&str, &dyn ToSql) tuples.
So you can do one of two things.
First, explicitly cast the first param value to the right type:
&[(":record_id", &record.id as &dyn ToSql),
(":dataset_id", &dataset_id),
(":value", "hello world")]
Or second, use the named_params! macro that rusqlite provides, which is arguably prettier:
statement.execute(named_params!{
":record_id": record.id,
":dataset_id": dataset_id,
":value": "hello world",
});

Assign reference to array of different sizes

I have a function which selects a different array based on whether a boolean is set to true or false, similar to the following:
const V1: [u8; 2] = [1,2];
const V2: [u8; 4] = [1,2,3,4];
fn test(b: bool) {
let v = if b { &V1 } else { &V2 };
}
fn main() {
test(false);
}
However I get the following error:
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:5:33
|
5 | let v = if b { &V1 } else { &V2 };
| --- ^^^ expected an array with a fixed size of 2 elements, found one with 4 elements
| |
| expected because of this
|
= note: expected type `&[u8; 2]`
found reference `&[u8; 4]`
I tried storing the constants as vectors, but to_vec cannot be used for constants.
An alternative would be to copy the array into a vector inside test, but I'd rather not have to make copies every time.
Is there a way to do this without copying the array every whenever the function is called?
The answer is to use slices, but unfortunately the rust type inference isn't clever enough to realize that. If you annotate the type of v explicitly as an &[u8] then everything should compile.
const V1: [u8; 2] = [1,2];
const V2: [u8; 4] = [1,2,3,4];
fn test(b: bool) {
let v: &[u8] = if b { &V1 } else { &V2 };
}
fn main() {
test(false);
}
Rust must know the type and lifetime of all variables at compile time; in this case, you are using the [u8] type, which is a slice type for u8 elements. Slices are stored as a reference to the first element as well as the number of elements. For example, your V1 slice stores u8 elements and there are 2 of them and V2 stores u8 elements and there are 4 of them. But note that these two are not the same type because of the difference in the number of elements.
So, if you'd like to return a borrowed value of one of the two slices (V1 or V2) you are able to do so as long as the compiler has two pieces of information; the type and their lifetime. We know that the compiler can figure out the type of both V1 and V2 since it is explicitly declared and they both live in static memory (data is part of the program source), so all we have to do is say that we are returning a reference to a slice (borrow) of u8s and they will be around for as long as the program is running (static lifetime). And even though V1 and V2 are not the same type, they look the same when you borrow them since all we are saying is that the return value references a bunch of u8 elements and we leave it up to the compiler to make sure it knows the number of elements for each at compile time. Check out the working example below.
const V1: [u8; 2] = [1,2];
const V2: [u8; 4] = [1,2,3,4];
fn test(b: bool) -> &'static [u8] {
let v: &[u8] = if b { &V1 } else { &V2 };
v
}
fn main() {
println!("{:?}", test(false));
}
As a final note, when attempting to solve these problems, don't be afraid to make mistakes; the compiler is actually quite friendly and very helpful when trying to figure out what to do next as shown in the error message below.
|
6 | fn test(b: bool) -> &[u8] {
| ^ help: consider giving it an explicit bounded or 'static lifetime: `&'static`
|
= help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments

Resources