'Is there a way to make a macro replace things in strings?

This macro should be able to replace entries in a string via an argument. For example, this would work:

let string = "Hello, world!";
replace_macro!(string, "world", "Rust"); // Hello, Rust!

I'm not sure how to do this, as all my previous attempts of just writing a regular function and calling that don't work inside macros. If possible, I'd like to be using macro_rules as opposed to a proc macro.



Solution 1:[1]

It is not possible. Macros cannot inspect and/or change the value of variables.

It is possible if the literal is embedded in the call (replace_macro!("Hello, world!", "world", "Rust");) but requires a proc-macro: macro_rules! macros cannot inspect and/or change literals.

It's a rather simple with a proc macro:

use quote::ToTokens;
use syn::parse::Parser;
use syn::spanned::Spanned;

type Args = syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>;

#[proc_macro]
pub fn replace_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input_span = input.span();

    let args = match Args::parse_terminated.parse(input) {
        Ok(args) => Vec::from_iter(args),
        Err(err) => return err.into_compile_error().into(),
    };
    let (original, text, replacement) = match args.as_slice() {
        [original, text, replacement] => (original.value(), text.value(), replacement.value()),
        _ => {
            return syn::Error::new(
                input_span,
                r#"expected `"<original>", "<text>", "<replacement>"`"#,
            )
            .into_compile_error()
            .into()
        }
    };

    original
        .replace(&text, &replacement)
        .into_token_stream()
        .into()
}

It parses a list of three string literals, punctated by commas, then calls str::replace() to do the real work.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chayim Friedman