Syntactic sugar for &str to String conversion

I don't think we need syntax for paths or osstrings. The primary reason that cstrs got syntax is because of the tricky null terminator. Other strings don't have this problem and can trivially use functions like Path::new.

7 Likes

Personally, I think the downside of this approach is that it would be the first time something that looks like a literal contains active code operations. And it would also be the first kind of literal that implicitly requires std, in particular after effords have been made to remove box, which actually was more them just semantic suger.

I also don't think we would need a macro for this. Just adding a line

use String::from as s;

to the prelude would generate a fairly short syntax for string generation.

A macro s! in line with vec! might still be the more desired option.

7 Likes

While new literals are being added, I’ll point out that they don’t have to have just one type. We have integer literals today that work with any primitive integer type and default to i32; we can have f-string literals that work with either fmt::Arguments or String (though we’d have to pick a default).

4 Likes

That wouldn't be a problem of literals were programmable and extendable in library code, rather than built-in. In that case string literals in #[no_std] code would just error out due to undefined String.

C++ has precedent for user-defined literals. Whether it's a good idea is debatable, but it's certainly an option.

Note that this would require a language change as associated functions are not currently importable. (There is now an RFC to import trait functions, but I’m not sure if it accounts for importing specific trait fn impls.)

fn s(v: impl Into<String>) -> String {
    v.into()
} 

(or something like that, wrote it on my phone, untested)

3 Likes

I like that idea. it reminds me of perl's regex operators where normally I might do s/dir1\/dir2/dir3/ but where for readability and ease I could just s#dir1/dir2#dir3#

Was another type of macro considered as a solution already?

E.g.

#[proc_macro_prefix]
pub fn f(s: TokenStream) -> TokenStream {
    // check that s is string literal
   quote! { format!(#s) }
}

It looks pretty extendible, solves the issue with #[nostd], and is compatible with existing prefixes.

Why does it need to be a procedural macro?

macro_rules! f {
    ( $($t:tt)+ ) => { format!( $($t)+ ) };
}

But it's essentially only a shortcut for format!(), and only saves you 5 letters...

2 Likes

I'm not sure. Perhaps it could be some form of macro_rules! or prefix_rules! too. I just thought that proc macros give more flexibility, e.g. they could let the code iterate over the characters of the literal and also make a guarantee that it's invoked only on (string) literals.

Regarding the last sentence you wrote, I'm strongly convinced that it is not about "just 5 letters" but the reduced cognitive load. E.g. see how

format!("foo {}", foo);

evolved into

format!("foo {foo}");

which reduces only 2 characters but is way easier to both, type and read. Then

f"foo {foo}"

takes it even further, thanks to removed parenthesis which always causes a "nested scope" effect in our brains. Additionally, the non-letter ! sign is also removed making the whole expression even simpler for humans. Although f!"foo {foo}" looks ok-ish too. But since it is a special case anyway, I think it could be removed for free too.

In the end, you could claim that foo()? also reduced only 5 characters compared to try!(foo()). But I guess everyone agrees ? is way more readable.

4 Likes
format!("foo {}", foo);

evolved into

format!("foo {foo}");

which reduces only 2 characters but is way easier to both, type and read.

It also turns positional arguments into named, doesn't matter here but before if you had a lot of them you either had to be very careful counting or write something like

format!("foo {foo}", foo = foo);

While this

f"foo {foo}"

replaces some extra characters and parens with a new concept - string literal type with built in interpolation.

built in interpolation.

I think that if prefixes were (special kind of) macros, they wouldn't be builtin. We could have something similar to what @chrefr mentioned in the std lib:

prefix_rules! f {
  ($s:str_literal) => { format!($s) };
}

Leaving here another invention, not sure how stupid it is:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};

#[proc_macro_attribute]
pub fn f(_: TokenStream, input: TokenStream) -> TokenStream {
    // Parse the input as a string literal
    let input = parse_macro_input!(input as LitStr);

    quote! { format!(#input) }.into()
}

#![feature(stmt_expr_attributes)]
#![feature(proc_macro_hygiene)]

use invention::f;

fn main() {
    let x = 42;
    let s = #[f]"foo x = {x}";
    println!("{s}");
}
4 Likes

I pushed this experiment a bit further and developed some prefixes. Asked for comments in the reddit thread here

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.