From 2c088d03cec73b25ae1a8ffa87a5ea6598904c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabor=20K=C3=B6rber?= Date: Thu, 25 Jan 2024 21:59:34 +0100 Subject: [PATCH] code: side-feature: exploring string into solution. will probably be moved away --- Cargo.lock | 9 +++++ Cargo.toml | 1 + src/bin/into.rs | 30 +++++++++++++++ src/bin/strinto_declarative.rs | 68 ++++++++++++++++++++++++++++++++++ strinto/.gitignore | 1 + strinto/Cargo.lock | 47 +++++++++++++++++++++++ strinto/Cargo.toml | 14 +++++++ strinto/src/lib.rs | 62 +++++++++++++++++++++++++++++++ 8 files changed, 232 insertions(+) create mode 100644 src/bin/into.rs create mode 100644 src/bin/strinto_declarative.rs create mode 100644 strinto/.gitignore create mode 100644 strinto/Cargo.lock create mode 100644 strinto/Cargo.toml create mode 100644 strinto/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d595b15..cbf1704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,10 +1258,19 @@ dependencies = [ "regex", "rodio", "serde", + "strinto", "tokio", "xxhash-rust", ] +[[package]] +name = "strinto" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.26", +] + [[package]] name = "symphonia" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 53fec25..635ea1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ rodio = "0.17.1" serde = { version = "1.0.171", features = ["derive"] } tokio = { version = "1.29.1", features = ["full"] } xxhash-rust = { version = "0.8.6", features = ["xxh3", "const_xxh3"] } +strinto = { path = "./strinto" } diff --git a/src/bin/into.rs b/src/bin/into.rs new file mode 100644 index 0000000..2fbfc04 --- /dev/null +++ b/src/bin/into.rs @@ -0,0 +1,30 @@ +#[macro_export] +macro_rules! into { + ($struct:ident { $($field:ident : $value:expr),* $(,)? }) => { + $struct { + $( + $field: $value.into(), + )* + } + }; +} + +struct SomeStruct { + first_name: String, + last_name: String, + age: usize, +} + +pub fn main() { + let last_name: String = "Last".to_string(); + let age: usize = 30; + + let x = into!(SomeStruct { + first_name: "First", + last_name: last_name, + age: age, + }); +} + +// see cargo expand to see how this function changes all struct inits to .into(). +// see also strinto, if you think "Why could it not be done in a macro as condition?" diff --git a/src/bin/strinto_declarative.rs b/src/bin/strinto_declarative.rs new file mode 100644 index 0000000..502c6e4 --- /dev/null +++ b/src/bin/strinto_declarative.rs @@ -0,0 +1,68 @@ +/// +/// Trying to get around "".into() for String values. +/// Or "".to_owned(). +/// Or String::from(""). +/// Or "".to_string(). +/// Choose your church. +/// +/// This is as far as you will get declaratively. + +#[macro_export] +macro_rules! strinto { + ($struct:ident { $($field:ident : $value:expr),* $(,)? }) => { + $struct { + $( + $field: $crate::strinto!(@convert $value), + )* + } + }; + + (@convert $value:literal) => { + match () { + _ if stringify!($value).starts_with("\"") => { + $value.to_string() + }, + _ => $value.into(), // <-- no getting rid of the into! + } + }; +} + +struct SomeStruct { + first_name: String, + //last_name: String, // NOPE because of @convert. + //age: usize, // reason of .into() in the first place. +} + +pub fn main() { + let x = strinto!(SomeStruct { + first_name: "First", + //last_name: String::from("Last"), // NOPE 2. + //age: 1, // NOPE 1. But I went further. + }); +} + +// while this compiles for only &str "", it is also useless compared to into! +// the reason is, that you cannot type check in the declarative macros. +// while yes, you would conditionally run stringify!($value) in the match, but the match expansion +// will always lead you to type errors if you don't do an .into() in the other arm. +// also in the end it fails to compile for anything but all members of the struct being Strings. + +// the last idea was going with a helper trait, but that will bleed into the runtime code, and yet again only work on pure String structs. + +// I guess I have to embrace String::from(), .to_string(), .to_owned(), .into() madness, for something every human reader can deduce in a second, +// if you would just implicitly convert &str to String if literally written in the code. + +// It is kinda amusing, that the other solution, to use new() kind of turns me off, because I cannot be explicit about the parameter name in the call. +// I would literally love the option to be more explicit in function param names. + +// while builder patterns feel a bit bloated for static runtime options, i will probably look into automations for that. + +// this is probably the only aesthetic decision in rust I probably will hate forever. +// It does not make sense, Strings as literals already are special in your code. +// Because numbers are as well, you dont have to write 123.into() either. +// I know I probably made some really harsh logical mistakes in my opinion here, and maybe it can be proven, that I am wrong, and I would love to hear that +// However it kind of feels like an excuse to not simplify assigning declaratively written &str to Strings in the code. + +// And it makes sense to be explicit about creating a String Buffer sometimes, but it does not make sense mostly. + +// Anyway, I will still try a procedural macro for this, just for fun. diff --git a/strinto/.gitignore b/strinto/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/strinto/.gitignore @@ -0,0 +1 @@ +/target diff --git a/strinto/Cargo.lock b/strinto/Cargo.lock new file mode 100644 index 0000000..7f8aab3 --- /dev/null +++ b/strinto/Cargo.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strinto" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/strinto/Cargo.toml b/strinto/Cargo.toml new file mode 100644 index 0000000..d47f7cc --- /dev/null +++ b/strinto/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "strinto" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full"] } +quote = "1.0" + +[dev-dependencies] +proc-macro2 = "1.0" diff --git a/strinto/src/lib.rs b/strinto/src/lib.rs new file mode 100644 index 0000000..b147e4d --- /dev/null +++ b/strinto/src/lib.rs @@ -0,0 +1,62 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Expr, ExprStruct}; + +/// # literal strings in a struct insantiation are converted .into(). +/// +/// ```rust +/// use strinto::strinto; +/// #[derive(Debug)] +/// struct TestStruct { +/// name: String, +/// title: String, +/// description: String, +/// age: usize, +/// } +/// +/// let descr = "description"; +/// +/// let output = strinto!(TestStruct { +/// name: "John", // Literal string. +/// title: String::from("Wicked"), +/// description: descr.to_string(), +/// age: 30, +/// }); +/// +/// let output_string = format!("{:?}", output); +/// assert_eq!( +/// output_string, +/// "TestStruct { name: \"John\", title: \"Wicked\", description: \"description\", age: 30 }" +/// ); +/// ``` + +#[proc_macro] +pub fn strinto(input: TokenStream) -> TokenStream { + let expr_struct = parse_macro_input!(input as ExprStruct); + + // Extract struct name and fields + let struct_name = &expr_struct.path; + let fields = expr_struct.fields.iter().map(|field| { + let field_name = field.member.clone(); + let field_value = &field.expr; + // Determine if the field value is a string literal and transform it + if let Expr::Lit(expr_lit) = field_value { + if let syn::Lit::Str(_) = expr_lit.lit { + quote! { #field_name: #field_value.into() } + } else { + quote! { #field_name: #field_value } + } + } else { + quote! { #field_name: #field_value } + } + }); + + let expanded = quote! { + #struct_name { + #(#fields,)* + } + }; + + expanded.into() +}