I'm a new to Rust.
I created a structure to hold system information.
pub struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
I want to pass this structure to the make_msg function to create a json body and send it as a request to another server.
fn make_msg(config: systemConfig) -> String{
let (host_name, cpus) = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> (String, usize){
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
// logical cpu count
let cpus = get_logical_cpus(&my_system);
(hostname, cpus)
}
I have two questions.
engine_name and group_name are values obtained from process argument. The reason that type is defined as Option is that its value is not required. If the engine name is not entered, the hostname is filled in. And If the group name is not entered, it is sent as ""(empty String).
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,,
more concise and intuitive)
None => host_name.clone(),
If clone() is not performed here, a borrow issue occurs. I'm looking for advice on whether using clone() is the right way, or if there is a better way.
I add test code
//cargo.toml
[dependencies]
sysinfo = "0.23.12"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
use sysinfo::{System, SystemExt};
use serde_json::json;
struct systemConfig {
pub admin_address: String,
pub engine_name: Option<String>,
pub group_name: Option<String>
}
fn main() {
let config = systemConfig {
admin_address: String::from("127.0.0.1:8080"),
engine_name: Some(String::from("hello")),
group_name: Some(String::from("world"))
};
let msg = make_msg(config);
println!("msg: {}", msg);
}
fn make_msg(config: systemConfig) -> String{
let host_name = get_system_info();
let engine_name = match config.engine_name {
Some(name) => name,
None => host_name.clone(),
};
let group_name = match config.group_name {
Some(name) => name,
None => String::from("")
};
let msg = json!({
"engineName": engine_name,
"groupName": group_name,
"hostName": host_name,
});
msg.to_string()
}
fn get_system_info() -> String {
use sysinfo::{ System, SystemExt };
// monitoring info
let mut my_system = System::new_all();
my_system.refresh_all();
// hostname
let hostname = get_hostname(&my_system);
hostname
}
pub fn get_hostname(s: &System) -> String {
s.host_name().unwrap()
}
I used the match syntax, but is there a more appropriate syntax? (if let Some/None,, more concise and intuitive)
Option has a few utilities that you could use. In the engine_name case, unwrap_or_else() is less verbose than your match:
let engine_name = config.engine_name
.unwrap_or_else(|| host_name.clone());
For group_name you can use unwrap_or_default() since the Default implementation on String returns an empty string:
let group_name = config.group_name.unwrap_or_default();
Note that both of these options are superior in this case to unwrap_or() since they do not require building the alternative value unless it's needed. For example, in the engine_name case this won't clone host_name unless config.engine_name is None.
I'm looking for advice on whether using clone() is the right way, or if there is a better way.
You can make it work using only references like this:
let engine_name = match &config.engine_name {
Some(ref name) => name,
None => &host_name,
};
Or, like above, you can use unwrap_or() (combined with as_ref()):
let engine_name = config.engine_name.as_ref().unwrap_or(&host_name);
However, the JSON Value::String variant requires an owned string, so not cloning here isn't really an optimization -- the json! macro will just clone it anyway.
Two questions, two answers:
The situation of unwrapping or replacing an Option is common enough that it got its own function: Option::unwrap_or:
let engine_name = config.engine_name.unwrap_or(host_name.clone());
let group_name = config.group_name.unwrap_or(String::from(""));
Clone is the right way. In some situations, engineName and hostName will contain the same string, so a .clone() will be required at some point either way.
Related
I can't figure out how to do import- and instancing-lines such that they tolerate non-existing files/modules and structs.
I tried making a macro that unwraps into such lines based on what files it finds in the directory, using a crate I found that had promise - include_optional - which allows to check for existence of files already at compile-time (since it's a macro).
However, I can't figure out how to use it properly in a macro, neither did I manage to use it without macro using the example at bottom of the docs conditional compilation chapter.
if cfg!(unix) { "unix" } else if cfg!(windows) { "windows" } else { "unknown" } (from the docs)
vs
if include_optional::include_bytes_optional!("day1.rs").is_some() { Some(&day01::Day01 {}) } else { None } // assume day1.rs and thus Day01 are non-existent (my attempt at doing same thing)
My if-statement compiles both cases, including the unreachable code (causing a compilation error), despite how according to the the docs it supposedly doesn't for cfg! ("conditional compilation").
Essentially, what I want is something of this form:
// Macro to generate code based on how many files/structs has been created
// There are anywhere between 1-25 days
get_days_created!;
/* // Let's assume 11 have been created so far, then the macro should evaluate to this code:
* mod day1;
* use day1 as day0#;
* // ...
* mod day11;
* use day11 as day11;
*
* // ...
* fn main() -> Result<(), i32> {
* let days : Vec<&dyn Day> = vec![
* &day01::Day01 {},
* // ...
* &day11::Day11 {},
* ];
* // ...
* }
*/
The solution is to create a proc_macro. These function similar to regular macros except they allow you to write a function of actual code they should execute, instead being given (and returning) a 'TokenStream' to parse the given tokens (and, respectively, what tokens the macro should expand to).
To create a proc_macro, the first and most important piece of information you need to know is that you can't do this anywhere. Instead, you need to create a new library, and in its Cargo.toml file you need to set proc-macro = true. Then you can declare them in its lib.rs. An example TOML would look something like this:
[package]
name = "some_proc_macro_lib"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
glob = "0.3.0"
regex = "1.7.0"
Then you can create your macros in this library as regular functions, with the #[proc_macro] attribute/annotation. Here's an example lib.rs with as few dependencies as possible. For my exact question, the input TokenStream is irrelevant and can be ignored, and instead you want to generate and return a new one:
use proc_macro::TokenStream;
use glob::glob;
use regex::Regex;
#[proc_macro]
pub fn import_days(_: TokenStream) -> TokenStream {
let mut stream = TokenStream::new();
let re = Regex::new(r".+(\d+)").unwrap();
for entry in glob("./src/day*.rs").expect("Failed to read pattern") {
if let Ok(path) = entry {
let prefix = path.file_stem().unwrap().to_str().unwrap();
let caps = re.captures(prefix);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day = &format!("{}", prefix);
let day_padded = &format!("day{:0>2}", n);
stream.extend(format!("mod {};", day).parse::<TokenStream>().unwrap());
if n < 10 {
stream.extend(format!("use {} as {};", day, day_padded).parse::<TokenStream>().unwrap());
}
}
}
}
return proc_macro::TokenStream::from(stream);
}
The question could be considered answered with this already, but the answer can and should be further expanded on in my opinion. And as such I will do so.
Some additional explanations and suggestions, beyond the scope of the question
There are however quite a few other crates beside proc_macro that can aid you with both parsing the input stream, and building the output one. Of note are the dependencies syn and quote, and to aid them both there's the crate proc_macro2.
The syn crate
With syn you get helpful types, methods and macros for parsing the input Tokenstream. Essentially, with a struct Foo implementing syn::parse::Parse and the macro let foo = syn::parse_macro_input!(input as Foo) you can much more easily parse it into a custom struct thanks to syn::parse::ParseStream. An example would be something like this:
use proc_macro2::Ident;
use syn;
use syn::parse::{Parse, ParseStream};
#[derive(Debug, Default)]
struct Foo {
idents: Vec<Ident>,
}
impl syn::parse::Parse for Foo {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut foo= Foo::default();
while !input.is_empty() {
let fn_ident = input.parse::<Ident>()?;
foo.idents.push(fn_ident);
// Optional comma: Ok vs Err doesn't matter. Just consume if it exists and ignore failures.
input.parse::<syn::token::Comma>().ok();
}
return Ok(foo);
}
}
Note that the syn::Result return-type allows for nice propagation of parsing-errors when using the sugary ? syntax: input.parse::<SomeType>()?
The quote crate
With quote you get a helpful macro for generating a tokenstream more akin to how macro_rules does it. As an argument you write essentially regular code, and tell it to use the value of variables by prefixing with #.
Do note that you can't just pass it variables containing strings and expect it to expand into identifiers, as strings resolve to the value "foo" (quotes included). ie. mod "day1"; instead of mod day1;. You need to turn them into either:
a proce_macro2::Ident
syn::Ident::new(foo_str, proc_macro2::Span::call_site())
or a proc_macro2::TokenStream
foo_str.parse::<TokenStream>().unwrap()
The latter also allows to convert longer strings with more than a single Ident, and manages things such as literals etc., making it possible to skip the quote! macro entirely and just use this tokenstream directly (as seen in import_days).
Here's an example that creates a struct with dynamic name, and implements a specific trait for it:
use proc_macro2::TokenStream;
use quote::quote;
// ...
let mut stream = TokenStream::new();
stream.extend(quote!{
#[derive(Debug)]
pub struct #day_padded_upper {}
impl Day for #day_padded_upper {
#trait_parts
}
});
return proc_macro::TokenStream::from(stream);
Finally, on how to implement my question
This 'chapter' is a bit redundant, as I essentially answered it with the first two code-snippets (.toml and fn import_days), and the rest could have been considered an exercise for the reader. However, while the question is about reading the filesystem at compile-time in a macro to 'dynamically' change its expansion (sort of), I wrote it in a more general form asking how to achieve a specific result (as old me didn't know macro's could do that). So for completion I'll include this 'chapter' nevertheless.
There is also the fact that the last macro in this 'chapter' - impl_day (which wasn't mentioned at all in the question) - serves as a good example of how to achieve two adjacent but important and relevant tasks.
Retrieving and using call-site's filename.
Parsing the input TokenStream using the syn dependency as shown above.
In other words: knowing all the above, this is how you can create macros for importing all targeted files, instantiating structs for all targeted files, as well as to declare + define the struct from current file's name.
Importing all targeted files:
See import_days above at the start.
Instantiating Vec with structs from all targeted files:
#[proc_macro]
pub fn instantiate_days(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
let re = Regex::new(r".+(\d+)").unwrap();
let mut stream = TokenStream::new();
let mut block = TokenStream::new();
for entry in glob("./src/day*.rs").expect("Failed to read pattern") {
match entry {
Ok(path) => {
let prefix = path.file_stem().unwrap().to_str().unwrap();
let caps = re.captures(prefix);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day_padded = &format!("day{:0>2}", n);
let day_padded_upper = &format!("Day{:0>2}", n);
let instance = &format!("&{}::{} {{}}", day_padded, day_padded_upper).parse::<TokenStream>().unwrap();
block.extend(quote!{
v.push( #instance );
});
}
},
Err(e) => println!("{:?}", e),
}
}
stream.extend(quote!{
{
let mut v: Vec<&dyn Day> = Vec::new();
#block
v
}
});
return proc_macro::TokenStream::from(stream);
}
Declaring and defining struct for current file invoking this macro:
#[derive(Debug, Default)]
struct DayParser {
parts: Vec<Ident>,
}
impl Parse for DayParser {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut day_parser = DayParser::default();
while !input.is_empty() {
let fn_ident = input.parse::<Ident>()?;
// Optional, Ok vs Err doesn't matter. Just consume if it exists.
input.parse::<syn::token::Comma>().ok();
day_parser.parts.push(fn_ident);
}
return Ok(day_parser);
}
}
#[proc_macro]
pub fn impl_day(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut stream = TokenStream::new();
let span = Span::call_site();
let binding = span.source_file().path();
let file = binding.to_str().unwrap();
let re = Regex::new(r".*day(\d+).rs").unwrap();
let caps = re.captures(file);
if let Some(caps) = caps {
let n: u32 = caps.get(1).unwrap().as_str().parse().unwrap();
let day_padded_upper = format!("Day{:0>2}", n).parse::<TokenStream>().unwrap();
let day_parser = syn::parse_macro_input!(input as DayParser);
let mut trait_parts = TokenStream::new();
for (k, fn_ident) in day_parser.parts.into_iter().enumerate() {
let k = k+1;
let trait_part_ident = format!("part_{}", k).parse::<TokenStream>().unwrap();
// let trait_part_ident = proc_macro::Ident::new(format!("part_{}", k).as_str(), span);
trait_parts.extend(quote!{
fn #trait_part_ident(&self, input: &str) -> Result<String, ()> {
return Ok(format!("Part {}: {:?}", #k, #fn_ident(input)));
}
});
}
stream.extend(quote!{
#[derive(Debug)]
pub struct #day_padded_upper {}
impl Day for #day_padded_upper {
#trait_parts
}
});
} else {
// don't generate anything
let str = format!("Tried to implement Day for a file with malformed name: file = \"{}\" , re = \"{:?}\"", file, re);
println!("{}", str);
// compile_error!(str); // can't figure out how to use these
}
return proc_macro::TokenStream::from(stream);
}
A database API gives us the option to build a Filter object that will be passed onto a Query.
It offers a fluent API to build such Filter:
filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3)
The topics are pre-organised into a Vec<Option<String>>, which we can pass onto the filters obj, using the helper function:
fn add_topics(mut f: &Filter, topics: Vec<String>) {
if let Some(topic0) = topics.get(0) {
f = &f.topic0(topic0);
}
if let Some(topic1) = topics.get(1) {
f = &f.topic1(topic1);
}
if let Some(topic2) = topics.get(2) {
f = &f.topic2(topic2);
}
if let Some(topic3) = topics.get(3) {
f = &f.topic3(topic3);
}
}
Unfortunately, the db API doesn't offer a .topics() method that accepts a Vec<_>.
Still, is there any way to avoid duplication of the logic?
E.g. I'm proficient with JS/TS, in which the above can be written as:
const addTopics = (filter: Filter, topics: string[]) {
for (let i = 0; i < topics.length; ++i) {
if (topics[i]) {
filter = filter['topic' + i](topics[i]);
}
}
}
Rust, as a typed language, doesn't allow this as far as I know.
Is there any other way?
By a minimal reproducible example, Herohtar means something like:
struct Filter {}
impl Filter {
fn topic0(&self, topic: &str) -> &Self {
self
}
fn topic1(&self, topic: &str) -> &Self {
self
}
fn topic2(&self, topic: &str) -> &Self {
self
}
fn topic3(&self, topic: &str) -> &Self {
self
}
}
fn main() {
let topic0 = "";
let topic1 = "";
let topic2 = "";
let topic3 = "";
let filter = Filter {};
let f = filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3);
}
This abstracts away all of the details, while giving a clear example of what you mean. (If what I've written isn't what you mean, then that's the point of writing a minimal example.)
To build the function you're looking for, you need a way to map what method you want to call to each location. That's fairly straightforward, although it requires a somewhat advanced type:
fn add_topics(mut f: &Filter, topics: Vec<Option<String>>) {
// Fancy type because of the borrows that need a lifetime.
// It's just the signature for a method call. The `for` allows
// adding a lifetime.
type TopicAssigner = for<'a> fn(&'a Filter, &str) -> &'a Filter;
// Now, match up the methods in the order of the Vector
let assign_topic: Vec<TopicAssigner> = vec![
Filter::topic0,
Filter::topic1,
Filter::topic2,
Filter::topic3,
];
// And zip it with what you're passed
for (topic, assigner) in zip(topics, assign_topic) {
if let Some(topic) = topic {
f = (assigner)(f, &topic);
}
}
}
Note that this has a problem if topics has more values than are expected, so it would be nice to force it into a 4-element array instead:
fn add_topics(mut f: &Filter, topics: [Option<String>; 4]) {
But perhaps that's inconvenient for other reasons.
I have working example of a simple loop (mostly taken from the odbc crate's example):
use std::io;
use odbc::*;
use odbc_safe::AutocommitOn;
fn main(){
let env = create_environment_v3().map_err(|e| e.unwrap()).unwrap();
let conn = env.connect_with_connection_string(CONN_STRING).unwrap();
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
let mut sql_text = String::new();
println!("Please enter SQL statement string: ");
io::stdin().read_line(&mut sql_text).unwrap();
stmt = match stmt.exec_direct(&sql_text).unwrap() {
Data(mut stmt) => {
let cols = stmt.num_result_cols().unwrap();
while let Some(mut cursor) = stmt.fetch().unwrap() {
for i in 1..(cols + 1) {
match cursor.get_data::<&str>(i as u16).unwrap() {
Some(val) => print!(" {}", val),
None => print!(" NULL"),
}
}
println!();
}
stmt.close_cursor().unwrap()
}
NoData(stmt) => {println!("Query executed, no data returned"); stmt}
}
}
}
I don't want to create new Statements for each query, as I just can .close_cursor().
I'd like to extract the loop's body to a function, like this:
fn exec_stmt(stmt: Statement<Allocated, NoResult, AutocommitOn>) {
//loop's body here
}
But I just can't! The .exec_direct() method mutably consumes my Statement and returns another. I tried different ways to pass Statement arg to the function (borrow, RefCell, etc), but they all fail when using in a loop. I am still new to Rust, so most likely I just don't know something, or does the .exec_direct's Statement consumption makes it impossible?
There's no nice way to move and then move back values through parameters. It's probably best to copy what .exec_direct does and just make the return type of your function a statement as well.
The usage would then look like this:
let mut stmt = Statement::with_parent(&conn).unwrap();
loop {
stmt = exec_stmt(stmnt);
}
and your function signature would be:
fn exec_stmt(stmt: Statement<...>) -> Statement<...> {
match stmt.exec_direct() {
...
}
}
I probably wouldn't recommend this, but if you really wanted to get it to work you could use Option and the .take() method.
fn exec_stmt(some_stmt: &mut Option<Statement<...>>) {
let stmt = some_stmt.take().unwrap();
// do stuff ...
some_stmt.replace(stmt);
}
The odbc-safe crate tried to have each state transition of ODBC reflected in a different type. The odbc-api crate also tries to protect you from errors, but is a bit more subtle about it. Your use case would be covered by the the Preallocated struct.
The analog example from the odbc-api documentation looks like this:
use odbc_api::{Connection, Error};
use std::io::{self, stdin, Read};
fn interactive(conn: &Connection) -> io::Result<()>{
let mut statement = conn.preallocate().unwrap();
let mut query = String::new();
stdin().read_line(&mut query)?;
while !query.is_empty() {
match statement.execute(&query, ()) {
Err(e) => println!("{}", e),
Ok(None) => println!("No results set generated."),
Ok(Some(cursor)) => {
// ...print cursor contents...
},
}
stdin().read_line(&mut query)?;
}
Ok(())
}
This will allow you to declare a function without any trouble:
use odbc_api::Preallocated;
fn exec_statement(stmt: &mut Preallocated) {
// loops body here
}
I am trying to implement a function with an optional reference. I would like to be able to use it like this:
fn main() {
let x = 557483943;
test_function(x, None);
let params = parameters(x);
test_function(x, Some(¶ms));
}
where parameters(x) may take some time to execute. Therefore I would like to be able to pass an optional reference to the function. For now I use this as test code:
#[derive(Debug)]
pub struct Parameters {
a: u32,
b: u32,
}
pub fn parameters(x: u32) -> Parameters {
// would be a tedious calculation...
let a = x / 6453;
let b = x % 589703;
Parameters { a, b }
}
What I came up with is this:
pub fn test_function(x: u32, params: Option<&Parameters>) {
// tmp_params is needed as owner of the return value of
// parameters(x) that outlives the match statement
let tmp_params: Parameters;
let params = match params {
Some(params) => params,
None => {
tmp_params = parameters(x);
&tmp_params
},
};
println!("params: {:?}", ¶ms);
}
which works, but I am not sure if this is the idiomatic way to do this in rust.
Is there a way to avoid tmp_params and have the Parameters created in the match statement outlive the match statement itself?
Is there a better/more idiomatic way to do this?
Out of habit from interpreted programming languages, I want to rewrite many values based on their key. I assumed that I would store all the information in the struct prepared for this project. So I started iterating:
struct Container {
x: String,
y: String,
z: String
}
impl Container {
// (...)
fn load_data(&self, data: &HashMap<String, String>) {
let valid_keys = vec_of_strings![ // It's simple vector with Strings
"x", "y", "z"
] ;
for key_name in &valid_keys {
if data.contains_key(key_name) {
self[key_name] = Some(data.get(key_name);
// It's invalid of course but
// I do not know how to write it correctly.
// For example, in PHP I would write it like this:
// $this[$key_name] = $data[$key_name];
}
}
}
// (...)
}
Maybe macros? I tried to use them. key_name is always interpreted as it is, I cannot get value of key_name instead.
How can I do this without repeating the code for each value?
With macros, I always advocate starting from the direct code, then seeing what duplication there is. In this case, we'd start with
fn load_data(&mut self, data: &HashMap<String, String>) {
if let Some(v) = data.get("x") {
self.x = v.clone();
}
if let Some(v) = data.get("y") {
self.y = v.clone();
}
if let Some(v) = data.get("z") {
self.z = v.clone();
}
}
Note the number of differences:
The struct must take &mut self.
It's inefficient to check if a value is there and then get it separately.
We need to clone the value because we only only have a reference.
We cannot store an Option in a String.
Once you have your code working, you can see how to abstract things. Always start by trying to use "lighter" abstractions (functions, traits, etc.). Only after exhausting that, I'd start bringing in macros. Let's start by using stringify
if let Some(v) = data.get(stringify!(x)) {
self.x = v.clone();
}
Then you can extract out a macro:
macro_rules! thing {
($this: ident, $data: ident, $($name: ident),+) => {
$(
if let Some(v) = $data.get(stringify!($name)) {
$this.$name = v.clone();
}
)+
};
}
impl Container {
fn load_data(&mut self, data: &HashMap<String, String>) {
thing!(self, data, x, y, z);
}
}
fn main() {
let mut c = Container::default();
let d: HashMap<_, _> = vec![("x".into(), "alpha".into())].into_iter().collect();
c.load_data(&d);
println!("{:?}", c);
}
Full disclosure: I don't think this is a good idea.