1

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.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
  • A `macro_rules` macro cannot see inside of strings; it only sees that it was handed a literal (not even a string literal, at that). This would definitely take a proc macro. – BallpointBen Apr 09 '22 at 22:21
  • I've seen another StackOverflow thread that is doing something similar, so was wondering if it could be adapted. https://stackoverflow.com/questions/55951472/how-to-replace-one-identifier-in-an-expression-with-another-one-via-rust-macro – salvage_dev Apr 09 '22 at 22:23
  • @salvage_dev a) Identifiers can somewhat be inspected, literals can't. b) The macro doesn't create a new indentifier, only replaces with a pre-defined one. Creating new identifiers require a proc macro, too. – Chayim Friedman Apr 09 '22 at 22:29

1 Answers1

2

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.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • This looks like it would do exactly what I'd need. However, you can't write a proc-macro and use it in the same crate, and I don't really want you just publish work I did not write. Do you know of any crates that do this? A quick search doesn't reveal any. – salvage_dev Apr 09 '22 at 22:54
  • @salvage_dev I don't know, but I'm fine with you using this code :) – Chayim Friedman Apr 09 '22 at 23:14