Declaring compile time string templates and rendering them at runtime

I want to achieve something like the following

const TEMP: &str = "Hello {}";

fn render(name: String) {
    let rendered = format!(TEMP, &name);
}

Obviously format! does not work like this. But I am okay with any other crate/templating solution, as long as it's very simple. I just need substitution, no fancy things.

1 Like

Insofar as I am aware, you can't. At least, not how it looks like you want to do it.

As you've noted, format! doesn't accept a constant for the format string. However, neither can any other macro, so there cannot be any crates that let you do this.

Without being able to use macros, you'd have to fall back to runtime formatting, for which there is no benefit to using a compile-time constant.

I did wonder if you could get half way by writing a temp function that returns the result of format_args!("Hello {name}")... but it doesn't seem you can. You could return a String or accept a closure that is called with the formatter, but that's much less flexible than a "real" solution.

Actually, you can kind of do this, but it's messy:

macro_rules! named_format {
    (TEMP, $($t:tt)*) => { format!("Hello {0}", $($t)*) };
    (OTHER, $($t:tt)*) => { format!("You are looking {0} today", $($t)*) };
}

fn render(name: String) {
    let rendered = named_format!(TEMP, &name);
}

You could replace format! with format_args! for more flexibility, and it would still work.

The only remaining avenue I can think of would be to have a build script that takes a file with all your format strings in it and generates specialised formatting code for them. That has really bad ergonomics, though. A quick search didn't reveal anything like this.

I haven't found any relevant content, even though I have looked up a lot of information, I think what the author wants to achieve is a precompiled effect similar to SQL statements. Define a model first (for example, 'mode='Hello, {}' ') and then fill in the content as appropriate. But as you have handled, you have restructured it into a method under the macro. In my opinion, this is a bit inconvenient. If I want to pass' mode 'as a parameter to a method, one possibility is that I need to rewrite the content of the macro. However, at the beginning of the project, I cannot directly anticipate the data of the format string I need, and we cannot use format characters as normal String types.

macro_rules! named_format {
    (TEMP, $($t:tt)*) => { format!("Hello {0}", $($t)*) };
    (OTHER, $($t:tt)*) => { format!("You are looking {0} today", $($t)*) };
}

fn render(name: String) {
    let rendered = named_format!(TEMP, &name);
}

And as you said, such content seems to be unfriendly to later developers in terms of reading code.

You can use a macro instead of const.

Like this:

macro_rules! TEMP { () => { "Hello {}" } }

fn main() {
    println!(TEMP!(), "world");
    let s = format!(TEMP!(), "world");
    println!("{}", s);
}

2 Likes

@H2CO3 posted a reference to a crate for this. The format string can be created at runtime as well.

2 Likes

At this point you are right, I have given up on compile time formatting like I want. Seems fundamentally impossible.

To give some context, I am mainly looking at this problem for prompt templating purposes for LLMs

I am okay with runtime formatting as well considering that's what I am doing currently with Python. (we can still use Rust's excellent error handling system).

I mainly don't want to write large string literals inside functions, it takes away from the readability and just looks messy.

1 Like

Using the crate I linked to you can define the format at compile time as in your example in the initial post. You can pass the const name to the format! function in this crate. Did you take a look?

Or do you actually need something different -- a format! macro that works at compile time?

I think users being able to reconfigure templates without having to recompile your code is significantly more useful than any benefit from compile-time formatting.

The way you asked the question, I thought you were concerned about performance, but given that inference is many orders of magnitude slower than parsing a format string, I don't think it's a practical concern here. :stuck_out_tongue:

Yeah, just use a runtime formatting crate.

1 Like

I really like this, but I want the function to return an error for invalid/missing arguments and not just sub them with empty strings.

That's a reasonable request. Perhaps you could create an issue to make that change, and/or contribute it. And in the meantime you could pass arguments rather than using string interpolation.

I searched for similar crates but didn't find one that fits your use case. Maybe someone else knows of one?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.