I have the following code:
my #logs = split(",",$opts->{"logs"});
$opt_href->{"logs"} = \#logs;
It basically splits the $opts->{"logs"} by comma and keeps the array ref. Later I need to check if string exists in the $opt_href->{"logs"} array. Looking at this topic, I see that it's recommended to keep a hash, instead of array. I could just do:
my %logs;
for each my $log (split(",",$opts->{"logs"})) {
$logs{$log} = 1;
}
$opt_href->{"logs"} = \%logs;
Is there a better way to do this? Maybe a one/two liners?
my %logs = map { $_ => 1 } split /,/, $opts->{logs};
$opt_href->{logs} = \%logs;
Or, using the anonymous hash reference, constructed by { }
$opt_href->{logs} = { map { $_ => 1 } split /,/, $opts->{logs} };
Hello I want to access an specific array item based in a condition previously checked. I leave the code here:
elsif (scalar(#{$boss->bosses}) > 1) {
foreach my $pa (#{$boss->bosses}) {
my $p = My::Model::Group->new(id => $pa->group_id);
push(#$groups, $p);
$valid_pass = 1 if ($pa->checkPassword($self->param('password')));
}
if ($valid_pass) {
my $pa_id = $pa->id;
my $pa_partner_id = $pa->group_id;
}
else {
}
}
What I want to do is, if that if in the array that comes, I check if the password is correct, so if it's correct, then I want to take the id and the group_id of the array item to use it in a function to be able to log them in.
Your for loop is doing two things at once: producing a list of My::Model::Group objects in #$groups, and finding the first boss whose password checks out.
I suggest that you split them up into two clear operations, and the List::Util modules first operator is ideal for the second task
Here's how it would look. I've extracted the result of the method call $boss->bosses into a variable $bosses to avoid repeated calls to the method
Note that you don't need to apply scalar to an array when checking its size. The > and all the other comparators impose scalar context anyway
I've taken much of my code from your question, and I'm a little concerned that you extract values for $pa_id and $pa_partner_id and then just discard them. But I imagine that you know what you really want to do here
use List::Util 'first';
my $bosses = $boss->bosses;
if ( ... ) {
...;
}
elsif ( #$bosses > 1 ) {
#$groups = map { My::Model::Group->new( id => $_->group_id ) } #$bosses;
my $password = $self->param( 'password' );
my $pa = first { $_->checkPassword( $password ) } #$bosses;
if ( $pa ) {
my $pa_id = $pa->id;
my $pa_partner_id = $pa->group_id;
}
else {
...;
}
}
I'm trying to implement a macro to allow MATLAB-esque matrix creation. I've got a basic working macro but I still have a long way to go.
I want to be able to enforce the right structure (same number of elements in each row) but I'm not sure how to do this within the macro. I think I want to enforce that each internal repetition has the same length - is this something I can do?
Here is my code so far:
pub struct Matrix<T> {
pub cols: usize,
pub rows: usize,
pub data: Vec<T>
}
macro_rules! mat {
( $($( $x:expr ),*);* ) => {
{
let mut vec = Vec::new();
let mut rows = 0;
$(
$(
vec.push($x);
)*
rows += 1;
)*
Matrix { cols : vec.len()/rows, rows: rows, data: vec}
}
};
}
It works but as you can see isn't very safe. It has no restrictions on the structure.
I want to do a lot more with this macro but I think this is a good start!
Update:
Here is some playground code for a crappy implementation I worked out. If anyone has any better suggestions please let me know! Otherwise I'll close this myself.
macro_rules! count {
() => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
}
macro_rules! mat {
( $( $x:expr ),* ) => { {
let vec = vec![$($x),*];
Matrix { cols : vec.len(), rows: 1, data: vec }
} };
( $( $x0:expr ),* ; $($( $x:expr ),*);* ) => { {
let mut _assert_width0 = [(); count!($($x0)*)];
let mut vec = Vec::new();
let rows = 1usize;
let cols = count!($($x0)*);
$( vec.push($x0); )*
$(
let rows = rows + 1usize;
let _assert_width = [(); count!($($x)*)];
_assert_width0 = _assert_width;
$( vec.push($x); )*
)*
Matrix { cols : cols, rows: rows, data: vec }
} }
}
playground
The count! macro expands to a constant expression that represents the number of arguments it got as input. It's just a helper for the mat! macro. If you need to count a lot of items and the compiler can't cope with it, see the Counting chapter in The Little Book of Rust Macros, which has more complex macros for counting.
My version of the macro uses dummy variables and assignments to verify that the width of all rows are the same. First off, I changed the macro's pattern to handle the first row separately from the subsequent rows. The first variable, _assert_width0, is initialized with an array of units ((), which makes the array take no memory), with the size of the array being the number of items in the first row. Then, _assert_width is also initialized with an array of units, with the size of the array being the number of items in each subsequent row. Then, _assert_width is assigned to _assert_width0. The magic here is that this line will raise a compiler error if the width of a row doesn't match the width of the first row, since the types of the array won't match (you might have e.g. [(); 3] and [(); 4]). The error isn't super clear if you don't know what's going on in the macro, though:
<anon>:38:24: 38:37 error: mismatched types:
expected `[(); 3]`,
found `[(); 4]`
(expected an array with a fixed size of 3 elements,
found one with 4 elements) [E0308]
<anon>:38 _assert_width0 = _assert_width;
^~~~~~~~~~~~~
<anon>:47:13: 47:44 note: in this expansion of mat! (defined in <anon>)
<anon>:38:24: 38:37 help: see the detailed explanation for E0308
First, to quickly address the title of your question: see the Counting chapter in The Little Book of Rust Macros. To summarise: there is no direct way, you need to write a macro that expands to something you can count in regular code.
Now, to address your actual question: hoo boy.
It's not so much counting that you want, it's to fail at compile time if the sub-sequences have different lengths.
First of all, there's no clean way to trigger a compilation failure from a macro. You can trigger some other pre-existing error, but you can't control the actual error message.
Secondly, there's no easy way to do "variable" comparisons in macros at all. You can sometimes compare against a fixed token sequence, but you're not doing that here.
So it's doubly not-really-doable.
The simplest thing to do is check the lengths during construction at runtime, and return an error or panic if they don't match.
Is it actually impossible? I don't believe so. If you're willing to accept inscrutable error messages and a massive jump in complexity, you can check for length equality between two token sequences like so:
macro_rules! tts_equal_len {
(($_lhs:tt $($lhs_tail:tt)*), ($_rhs:tt $($rhs_tail:tt)*)) => {
tts_equal_len!(($($lhs_tail)*), ($($rhs_tail)*))
};
(($($_lhs_tail:tt)+), ()) => { do_something_bad!() };
((), ($($_rhs_tail:tt)+)) => { do_something_bad!() };
((), ()) => { do_something_good!() };
}
macro_rules! do_something_bad { () => { { println!("kaboom!") } } }
macro_rules! do_something_good { () => { { println!("no kaboom!") } } }
fn main() {
tts_equal_len!((,,,), (,,,));
tts_equal_len!((,,,), (,,));
tts_equal_len!((,), (,,));
}
Again, the real problem is finding some way to fail at compile time such that the user will understand why compilation failed.
Update: there's a new way of doing things
As of the day on which this was written, the feature of rust which enables the following (count) to be done, in still unstable and is available in nightly builds.
You can check out the github issues and test cases for further understanding of what's given below
To enable this feature, you need to add the line #![feature(macro_metavar_expr)] to the top of the crate's root module (usually main.rs or lib.rs), and also set your repo to use nightly builds, which is easily done by creating a file rust-toolchain.toml in the root directory (alongside Cargo.toml) and add the folllowing lines to it:
[toolchain]
channel = "nightly"
Now, instead of providing a solution to you specific problem, I'd like to share a generic solution I created to better illustrate most situations.
I highly recommend studying the code AND the comments, by pasting the following two code blocks in a file (main.rs).
The macro_rules
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
struct SumLen {
sum: i32,
len: u32
}
/// currently one `i32` type is available
///
/// # Examples
///
/// The output of the following:
/// ```ignore
/// sumnarr!(i32 => 5 ; 6, 7, 8)
/// ```
/// will be `[(5, 1), (21, 3)]`
macro_rules! sumnarr {
( $type:ty => $( $( $x: expr ),* );* ) => {
{
// `${count(x,0)}` will give you "length" (number of iterations)
// in `$( )*` loop that you are IMMEDIATELY OUTSIDE OF (e.g.: the `$( )*` loop below)
// `${count(x,1)}` will give you TOTAL number of iterations that the `$( )*` loop
// INSIDE of the IMMEDIATE `$( )*` loop will make. i.e. it is similar to converting
// [ [i1,i2], [i1,i2,i3] ] TO [ i1,i2,i3,i4,i5 ] i.e. flatten the nested iteration.
// In case of `[ [i1,i2], [i1,i2,i3] ]`, `${count(x,0)}` is 2 and `${count(x,1)}` is 5
let mut arr: [SumLen; ${count(x,0)}] = [SumLen{ sum:0, len:0}; ${count(x,0)}];
$(
// `${index()}` refers to the iteration number within the `$( )*` loop
arr[${index()}] = {
let mut sum = 0;
//let mut len = 0;
// THe following will give us length is the loop it is IMMEDIATELY OUTSIDE OF
// (the one just below)
let len = ${count(x,0)};
$(
sum += $x;
// If you were NOT using `$x` somewhere else inside `$( )*`,
// then you should use `${ignore(x)};` to inform the compiler
//You could use the below method, where `${length()}` will give you
//"length" or "number of iterations" in current loop that you are in
// OR
// you could go with my method of `${count(x,0)}` which is explained above
//len = ${length()};
)*
SumLen {
sum,
len
}
};
)*
arr
}
};
}
The #[test] (unit test)
#[test]
fn sumnarr_macro() {
let (a, b, c, d, e) = (4, 5, 6, 9, 10);
let sum_lens = [
SumLen {
sum: a + e,
len: 2
},
SumLen {
sum: b + c + d,
len: 3
}
];
assert_eq!(sum_lens, sumnarr!(i32 => a,e;b,c,d));
}
I hope this helps
Is it possible to create a macro which counts the number of expanded items?
macro_rules! count {
($($name:ident),*) => {
pub enum Count {
$(
$name = 1 << $i // $i is the current expansion index
),*
}
}
}
count!(A, B, C);
Here is a macro that counts the number of matched items:
macro_rules! count_items {
($name:ident) => { 1 };
($first:ident, $($rest:ident),*) => {
1 + count_items!($($rest),*)
}
}
fn main() {
const X: usize = count_items!(a);
const Y: usize = count_items!(a, b);
const Z: usize = count_items!(a, b, c);
assert_eq!(1, X);
assert_eq!(2, Y);
assert_eq!(3, Z);
}
Note that the counting is computed at compile time.
For your example, you can do it using accumulation:
macro_rules! count {
($first:ident, $($rest:ident),*) => (
count!($($rest),+ ; 0; $first = 0)
);
($cur:ident, $($rest:ident),* ; $last_index: expr ; $($var:ident = $index:expr)+) => (
count!($($rest),* ; $last_index + 1; $($var = $index)* $cur = $last_index + 1)
);
($cur:ident; $last_index:expr ; $($var:ident = $index:expr)+) => (
#[repr(C)]
enum Count {
$($var = 1 << $index),*,
$cur = 1 << ($last_index + 1),
}
);
}
pub fn main() {
count!(A, B, C, D);
assert_eq!(1, Count::A as usize);
assert_eq!(2, Count::B as usize);
assert_eq!(4, Count::C as usize);
assert_eq!(8, Count::D as usize);
}
Yes, if you pack it as array of idents:
macro_rules! count {
($($name:ident),*) => {
{
let counter = [$(stringify!($name),)*];
counter.len()
}
}
}
Count, names, reverse order of names are available. After, you can use it to construct something. For enum building you have to join it with something like this.
In this context, no. A macro could create an expression that counts the number of identifiers passed to it, but it would only be evaluated at runtime. I created this example in just a few minutes, but I realized it would not work for what you're doing.
Compiler plugins, however, are particularly suited to this sort of work. While they're not trivial to implement, I don't think it would be overly difficult to create one for this purpose. Maybe take a look, try your hand at it, and come back if you get stuck?
Since this question is general, posting an example of counting where arguments are separated by white-space (not commas).
Although in retrospect it seems obvious, it took me a while to figure out:
/// Separated by white-space.
#[macro_export]
macro_rules! count_args_space {
($name:ident) => { 1 };
($first:ident $($rest:ident) *) => {
1 + count_args_space!($($rest) *)
}
}
/// Separated by commas.
#[macro_export]
macro_rules! count_args_comma {
($name:ident) => { 1 };
($first:ident, $($rest:ident),*) => {
1 + count_args_comma!($($rest),*)
}
}
Second example is from #malbarbo, just posting to so you can see the 2x changes that were needed.
I have a subroutine that will return two hashes when all goes well. But the sub checkouts output of command and if it matches a certain pattern, it returns with "-1". Is there anyway to check the return of the subroutine from where I called it?
Kinda like:
if (RETURN_VALUE == -1){
do something}
else
go as normal with the hashes
How could one function return two hashes?
If you mean hashrefs, the check would be quite simple:
my ($h1,$h2) = myFunction();
if ( !ref($h1) || (ref($h1) ne "HASH"))
{
die 'error';
}
You function should return references to the two hashes on success and nothing upon failure. Then you can just check the truth value of the function call.
sub myfunc {
my %hash1;
my %hash2;
return (\%hash1, \%hash2);
}
my $ref1;
my $ref2;
unless (($ref1, $ref2) = myfunc()) {
print "Something went wrong\n";
} else {
print "OK\n";
}
If you return two (or any number for that matter) hashes from a subroutine, the result will be a single hash. You will not be able to separate the original hashes from the result in a normal manner. Returning hash references will not exhibit this problem.
Suppose foo() returns two hash references when the pattern is matched and returns -1 when it does not match.
my ( $value_1, $value_2 ) = foo;
if ( $value_1 == -1 ) {
# pattern did not match
}
else { # for strict checks: elsif ( ref $value_1 eq 'HASH' && ref $value_2 eq 'HASH' ) {
# pattern matched
}