Reducing match indentation for deeply nested properties - rust

I need to refer to a value deep within a structure which includes an Option nested in a struct property, nested in a Result.
My current (working) solution is:
let raw = &packet[16..];
match PacketHeaders::from_ip_slice(raw) {
Err(_value) => {
/* ignore */
},
Ok(value) => {
match value.ip {
Some(Version4(header)) => {
let key = format!("{}.{}.{}.{},{}.{}.{}.{}",
header.source[0], header.source[1], header.source[2], header.source[3],
header.destination[0], header.destination[1], header.destination[2], header.destination[3],
);
let Count {packets, bytes} = counts.entry(key).or_insert(Count {packets: 0, bytes: 0});
*packets += 1;
*bytes += packet.len();
if p > 1000 { /* exit after 1000 packets */
for (key, value) in counts {
println!("{},{},{}", key, value.packets, value.bytes);
}
return ();
}
p += 1;
}
_ => {
/* ignore */
}
}
}
}
(The problem with my current code is the excessive nesting and the two matches.)
All I want is PacketHeaders::from_ip_slice(ip) >> Ok >> ip >> Some >> Version4.
How can I get this, or ignore a failure nicely (NOT crash/exit) for each captured packet?

Pattern matching nests, and the pattern for struct looks like struct literals with the addition that .. means "and match all the rest, which I don't care about", similar to how the _ pattern means "match anything". Meaning you can do
match PacketHeaders::from_ip_slice(raw) {
Ok(PacketHeaders { ip: Version4(header), .. }) => {
/* handle */
}
_ => {
/* ignore `Err(_)` and other `Ok(_)` */
},
}
and if you're going to ignore all but one case, you can use if let:
if let Ok(PacketHeaders { ip: Version4(header), .. }) = PacketHeaders::from_ip_slice(raw) {
/* handle */
}

Code ended up being:
let raw = &packet[16..];
if let Ok(PacketHeaders { ip: Some(Version4(header)), link: _, vlan: _, transport: _, payload: _}) = PacketHeaders::from_ip_slice(raw) {
let key = format!("{}.{}.{}.{},{}.{}.{}.{}",
header.source[0], header.source[1], header.source[2], header.source[3],
header.destination[0], header.destination[1], header.destination[2], header.destination[3],
);
let Count {packets, bytes} = counts.entry(key).or_insert(Count {packets: 0, bytes: 0});
*packets += 1;
*bytes += packet.len();
}

Related

How can I add a global keydown event listener in Dioxus)?

I'm currently trying out dioxus for rust, and I'm trying to figure out how to handle a global keyboard down event.
I want to use the arrow keys to move images back and forth:
Here's my code so far:
use dioxus::{events::*, prelude::*};
use log::{info, LevelFilter};
/**
* Specify <link data-trunk rel="copy-dir" href="src/assets" />
* in the index.html to copy the files!!
*
* You'll see them in the dist directory!
*/
fn main() {
dioxus_logger::init(LevelFilter::Info).expect("failed to init logger");
dioxus::web::launch(app);
}
fn app(cx: Scope) -> Element {
let mut index = use_state(&cx, || 1);
let change_evt = move |evt: KeyboardEvent| match evt.key.as_str() {
"ArrowRight" => index += 1,
"ArrowLeft" => index -= 1,
_ => {}
};
let url = format!("/assets/img/wallpaper/1042/0{}.jpg", index);
cx.render(rsx!(img {
src: "{url}",
onkeydown: change_evt,
}))
}
In JavaScript would've been something like
document.addEventListener('keydown', (evt) => {
// Do magic
}
I've tried following the calculator example but can't get it to work.
Any ideas?
onkeydown does not seem to work as a callback passed to an image. Wrap it in a div.
I placed an extra button there because, for some reason, the keyboard event callbacks did not register until I interacted with the app somehow (tried it in the browser).
fn app(cx: Scope) -> Element {
let mut index = use_state(&cx, || 1);
let change_evt = move |evt: KeyboardEvent| {
log::info!("{index}{}", evt.key);
match evt.key.as_str() {
"ArrowRight" => index += 1,
"ArrowLeft" => index -= 1,
_ => {}
}
};
let url = format!("/assets/img/wallpaper/1042/0{}.jpg", index);
cx.render(rsx!(
img {
src: "{url}",
}
div {
class: "display",
onkeydown: change_evt,
button {
class: "but",
onclick: move |evt| {
println!("{evt:?}");
info!("{evt:?}");
},
"Press me!"
},
},
))
}

Refactoring variable assignment thru if let block with Enum

I'm working with the code below, which works, but is clearly not a very clever or efficient way to write a value to res.
let mut res = "";
if let Video(n) = res_info { // res_info represents reference to &Settings type
if n.pixel_width > 1920{
res = "2160p";
}
else{
res = "1080p";
}
}
Printing res_info would yield the following:
Video(Video { pixel_width: 1920, pixel_height: 1080})
The following code seems to be close, however it's not assigning &str to res. I would much prefer a codeblock like this, in which res is only declared once.
let res = if let Video(n) = res_info {
if n.pixel_width > 1920 {
"2160p";
}
else{
"1080p";
}
};
As per the unit documentation
The semicolon ; can be used to discard the result of an expression at the end of a block, making the expression (and thus the block) evaluate to ()
Removing the semicolon should stop value from being discarded so the &str is resolved from the if blocks.
let res = if let Video(n) = res_info {
if n.pixel_width > 1920{
"2160p"
} else{
"1080p"
}
}else{
panic!("res_info is not a Video")
};
or with a match statement might be cleaner
let res = match res_info {
Video(n) if n.pixel_width > 1920 => "2160p",
Video(n) => "1080p",
_ => panic!("res_info is not a Video")
};

Expanding a recursive macro in rust, similar to serde_json but for HTML elements

#[macro_export]
macro_rules! reactant {
// Converts (function) {...} into element
( $f:ident $t:tt ) => {
{
let mut elem = HtmlElement::new($f);
reactant!(#expand elem $t);
elem
}
};
// Expands {...} and parses each internal node individually
( #expand $self:ident { $($t:tt),* } ) => {
$(
reactant!(#generate $self $t);
)*
};
// Takes a (function) {...} node, feeds it back recursively, and pushes it into parent
( #generate $self:ident $t1:tt $t2:tt) => {
{
$self.push_inner(reactant!($t1 $t2));
}
};
// Takes a literal and sets the parent internal to a string
( #generate $self:ident $l:literal) => {
{
$self.set_inner(String::from($l)).unwrap();
}
};
}
#[allow(unused_macros)]
#[cfg(test)]
mod tests {
use crate::html::types::*;
use crate::html::HtmlElement;
use crate::reactant;
#[test]
fn test() {
// Doesn't work, not expecting '{' after second div, although the first one works fine
let x = reactant!(div {
div {
"test"
}
});
// Works, outputs <div>thing</div>
let y = reactant!(div {
"hello",
"thing"
});
}
}
I am working on making an uncreatively named HTML library in Rust, and am also learning macros at the same time (the macro documentation is confusing). Part of the project is making a macro that generates HTML elements recursively to make a document, in a similar appearance to serde_json. The syntax is shown in the test cases. Basically, every HTML element (div, h1, etc.) is mapped to a function that outputs a struct that I have crafted for HTML elements. I managed to get the macro working in one way, but it only allowed for HTML children when I want it to also take literals to fill in, say, an h1 with a string (test case 1). Test case 2 shows where it doesn't work, and I am not understanding why it can't read the {.
Let's try to trace what happens when the macro is being expanded:
reactant!(div { div { "test" } });
This triggers the first rule: $f:ident $t:tt with:
$f set to div
$t set to { div { "test" } }
Which gets expanded to:
reactant!(#expand elem { div { "test" } });
I believe you intended the second step to trigger rule number 2: #expand $self:ident { $($t:tt),* } with:
$self set to elem
$t set to div { "test" }
Which would get expanded to:
reactant!(#generate elem div { "test" });
But div { "test" } is actually two tts and so can't be parsed by this rule (or by any other rule).
If my interpretation of your intentions is correct, you will need to have separate rules to handle each case and process the list iteratively:
// Expands {...} and parses each internal node individually
( #expand $self:ident { $t1:tt $t2:tt, $($tail:tt)* } ) => {
reactant!(#generate $self $t1 $t2);
reactant!(#expand $self { $($tail)* })
};
( #expand $self:ident { $t:tt, $($tail:tt)* } ) => {
reactant!(#generate $self $t);
reactant!(#expand $self { $($tail)* });
};
// Duplicate rules to handle the last item if it's not followed by a ","
( #expand $self:ident { $t1:tt $t2:tt } ) => {
reactant!(#generate $self $t1 $t2);
};
( #expand $self:ident { $t:tt } ) => {
reactant!(#generate $self $t);
};
Playground

Unrolling tree-recursion into an iterative program

I wrote a library that can generate arbitrary strings given a spec-object (https://github.com/rgrannell1/revexp) and I want to convert the function that reads the spec from a recursive algorithm to an iterative algorithm. I'm running into stackoverflow errors due to the depth of the specs I'm traversing.
I believe I need to move from using the call-stack to an explicit stack, but I've never done this before. I've read through previous posts on StackOverflow but I didn't fully understand how to apply the solutions to this problem.
Here is an example spec object.
const data = {
every: [
{
digit: { zero: false }
},
{
repeat: {
value: { digit: {} }
}
}
]
}
and a minimal example of how the algorithm currently traverses the spec and generates a string matching the spec.
const fromSpec = (data) => {
if (data.every) {
return fromSpec.every(data)
} else if (data.digit) {
return fromSpec.digit()
} else if (data.repeat) {
return fromSpec.repeat(data.repeat)
}
}
fromSpec.digit = () => {
return Math.floor(Math.random() * 10)
}
fromSpec.every = part => {
let message = ''
for (const elem of part.every) {
message += fromSpec(elem)
}
return message
}
fromSpec.repeat = part => {
let message = ''
// -- just using fixed repeat for the example
for (let ith = 0; ith < 10; ++ith) {
message += fromSpec(part.value)
}
return message
}
const result = fromSpec(data)
result // 1034856872
I'd appreciate any advice on how to traverse this data-structure and generate an output string in an iterative rather than recursive fashion.
The following example modifies the code to use a stack data structure. Data on the stack is processed incrementally, with new data possibly added on each iteration.
const fromSpec = (data) => {
const stack = [data];
let message = '';
while (stack.length > 0) {
const item = stack.pop();
// Assumption based on the code in the question:
// 'every', 'digit', and 'repeat' keys are mutually exclusive.
if (item.every) {
// Add items in reverse order, so that items are popped off the stack
// in the original order.
for (let i = item.every.length - 1; i >= 0; --i) {
stack.push(item.every[i]);
}
} else if (item.digit) {
message += String(Math.floor(Math.random() * 10));
} else if (item.repeat) {
for (let i = 0; i < 10; ++i) {
stack.push(item.repeat.value);
}
}
}
return message;
}
An alternative approach would be required for a more complicated scenario (e.g., when a node in the tree requires processing both 1) when it's initially encountered in the traversal and 2) after all its children have been traversed).
The following links may be relevant.
https://web.archive.org/web/20120227170843/http://cs.saddleback.edu/rwatkins/CS2B/Lab%20Exercises/Stacks%20and%20Recursion%20Lab.pdf
https://web.archive.org/web/20161206082402/https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/recursionConversion/page/recursionConversion.html
It's natural to write RevExp.toString as a recursive program because it is expected to process a recursively structured input. However because recursive programs can lead to deep stacks, it's not uncommon to flatten a recursive process to an iterative one.
Good programmers know that maintaining complexity goes a long way in sustaining our sanity. I want to keep my recursive program and I want the computer to handle flattening the process for me. Can I have my cake and eat it too?
Here's another way to look a the problem -
// example1.js
import { concat, upper, digit, str, toString } from './RevExp.js'
const licensePlate =
concat(upper(), upper(), upper(), str("-"), digit(), digit(), digit())
console.log(toString(licensePlate))
console.log(toString(licensePlate))
console.log(toString(licensePlate))
// RFX-559
// VKT-794
// KSF-823
Let's begin writing the RevExp module. We'll start by creating constructors for each of our expression types -
// RevExp.js
const str = (value = "") =>
({ type: str, value })
const lower = () =>
({ type: lower })
const upper = () =>
({ type: upper })
const digit = ({ zero = true } = {}) =>
({ type: digit, zero })
const concat = (...exprs) =>
({ type: concat, exprs })
Now let's work on RevExp.toString -
// RevExp.js (continued)
import { inRange } from './Rand.js'
const toString = (e) =>
{ switch (e.type)
{ case str:
return String(e.value)
case lower:
return String.fromCharCode(inRange(97, 122))
case upper:
return String.fromCharCode(inRange(65, 90))
case digit:
return e.zero
? String(inRange(0, 9))
: String(inRange(1, 9))
case concat:
return e.exprs.reduce((r, v) => r + toString(v), "")
default: throw Error(`unsupported expression type: ${e.type}`)
}
}
export { lower, upper, digit, alpha, repeat, concat, str, toString }
It should be possible to make complex expressions by combining several simple expressions. And we imagine some new types like alpha, and repeat -
// example2.js
import { alpha, digit, repeat, concat, str, toString } from './RevExp.js'
const segment =
concat(alpha(), digit(), alpha(), digit(), alpha())
const serial =
concat
( repeat
( concat(segment, str("-"))
, { count: 4 }
)
, segment
)
console.log(toString(serial))
console.log(toString(serial))
console.log(toString(serial))
// F3Q7U-b6k8Q-R8e3A-a2q3M-j0a9k
// g6G3w-h2O3O-b8O3k-L4p1y-m5I0y
// m6E0M-A4C2y-K3g0M-d7X7j-w8v5G
And add corresponding support in the RevExp module -
// RevExp.js (enhanced)
import { inRange, sample } from './Rand.js'
const str = // ...
const lower = // ...
const upper = // ...
const digit = // ...
const concat = // ...
const alpha = () =>
oneOf(upper(), lower())
const oneOf = (...exprs) =>
({ type: oneOf, exprs })
const repeat = (expr = {}, { count = 10 } = {}) =>
({ type: repeat, expr, count })
const toString = (e) =>
{ switch (e.type)
{ case str: // ...
case lower: // ...
case upper: // ...
case digit: // ...
case concat: // ...
case oneOf:
return toString(sample(e.exprs))
case repeat:
return toString(concat(...Array(e.count).fill(e.expr)))
default: // ...
}
}
export { /* ..., */ alpha, oneOf, repeat }
Now let's transform the recursive program to an iterative one. And without having to think about the stack or mutate state as the program runs, too!
// RevExp.js (stack-safe)
// ...
import * as Str from './Str.js'
import { loop, recur, call } from './TailRec.js'
// ...
const toString = (e = {}) =>
loop(toStringTailRec, e)
const toStringTailRec = e =>
{ switch (e.type)
{ case str: // ...
case lower: // ...
case upper: // ...
case digit: // ...
case concat:
return e.exprs.length
? call
( Str.concat
, recur(e.exprs[0])
, recur(concat(...e.exprs.slice(1)))
)
: Str.empty
case oneOf:
return recur(sample(e.exprs))
case repeat:
return recur(concat(...Array(e.count).fill(e.expr)))
default: throw Error(`unsupported expression type: ${e.type}`)
}
}
export { /*...*/, toString } // <-- don't export toStringTailRec helper
And here are the remaining modules, Str, Rand, and TailRec -
// Str.js
const empty =
""
const concat = (a = "", b = "") =>
a + b
export { empty, concat }
// Rand.js
const rand = (n = 2) =>
Math.floor(Math.random() * n)
const inRange = (min = 0, max = 1) =>
rand(max - min + 1) + min
const sample = (t = []) =>
t[rand(t.length)]
export { rand, inRange, sample }
Writing modules is an important factor in creating reusable code. This TailRec module was written in another post and can be reused, without modification1, to meet our program's needs.
Now we can rely on recursively structured programs without having to introduce complexity or requiring a change in how we think every time we encounter a recursive problem. Write the module once, reuse as needed -
// TailRec.js
const identity = x =>
x
const call = (f, ...values) =>
({ type: call, f, values })
const recur = (...values) =>
({ type: recur, values })
const loop = (f, ...init) =>
{ const aux1 = (e, k) =>
e.type === recur
? call(aux, e.values, r => call(aux1, f(...r), k))
: e.type === call
? call(aux, e.values, r => call(aux1, e.f(...r), k))
: call(k, e)
const aux = (exprs, k) =>
call
( exprs.reduce
( (mr, e) =>
k => call(mr, r => call(aux1, e, x => call(k, [ ...r, x ])))
, k => call(k, [])
)
, k
)
return run(aux1(f(...init), identity))
}
const run = r =>
{ while (r && r.type === call)
r = r.f(...r.values)
return r
}
export { loop, call, recur }
In the end, the approach here is virtually the same as yours. However instead of representing expressions writing JS objects by hand, we use functions, which can be parameterized and composed, and can handle the tedious and precarious assembly for us. Programmer sanity maintained -
// example2.js
// ...
console.log(serial)
{ type: concat
, exprs:
[ { type: repeat
, expr:
{ type: concat
, exprs:
[ { type: concat
, exprs:
[ { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
]
}
, { type: str, value: "-" }
]
}
, count: 4
}
, { type: concat
, exprs:
[ { type: concat
, exprs:
[ { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
, { type: digit, zero: true }
, { type: oneOf, exprs: [ { type: lower }, { type: upper } ] }
]
}
]
}
]
}
I hope this was seen as an exciting way to see the same problem from a different perspective. If you end up using the TailRec module, see the original post for additional explanation. I'm happy to answer any follow-up questions.
1. Minor formatting changes and variable renaming for consistency with this answer

Gtk Widgets returning None even when they hold data

I have a filechoosernative and a comboboxtext in my UI. Now I am trying to extract data from those two inside callbacks but they are returning me None even though they clearly have data set by the user. Why is this happening?
Excerpt from https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L41
fn get_selected_file(&self) -> Option<std::path::PathBuf> {
let selected_file = self.fcn.get_filename();
dbg!(&selected_file);
selected_file
}
Excerpt from https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L35
fn get_selected_device(&self) -> Option<udisks::DiskDevice> {
// Combo box text only stores a Gstring (Device ID)
// Search through the list of devices from udisks2 again
// and find the device with matching device ID
let selected_device = match self.lsblk_cbt.get_active_text() {
Some(txt) => {
dbg!(&txt);
for disk in crate::aux::backend::get_disks() {
if disk.drive.id == txt {
return Some(disk);
}
}
dbg!("No matching device found. Must reload.");
None
}
None => {
dbg!("lsblk_cbt is returning nothing");
None
}
};
dbg!(&selected_device);
selected_device
}
Both return None in https://gitlab.com/9898287/nixwriter/-/blob/rir/src/frontend/mod.rs#L110
fn set_lsblk_cbt(&mut self) {
let cbt = self.lsblk_cbt.clone();
for ddev in crate::aux::backend::get_disks() {
cbt.append_text(&ddev.drive.id);
}
let (device_chosen, file_chosen) = (
self.get_selected_device().is_some(),
self.get_selected_file().is_some(),
);
let start = self.start.clone();
cbt.connect_changed(move |_| {
start.set_sensitive(device_chosen && file_chosen);
dbg!("From set_lsblk_cbt", device_chosen, file_chosen);
});
}
even after the user has set a file and selected an item from ComboboxText.

Resources