Pre-RFC: pattern matching on strings

I was just reading about this feature being included in Scala.

val s"$greeting, $name" = "Hello, James"
println(greeting)  // "Hello"
println(name)  // "James"
val TimeSplitter = "([0-9]+)[.:]([0-9]+)".r
val s"The time is ${TimeSplitter(hours, mins)}" = "The time is 10.50"
println(hours) // 10
println(mins) // 50

And it occurred to me that this would make quick parsing of customized string formats fairly easy, and can be seen as the dual of tools like format!().

Are there others who would consider this a useful feature? If not, why?

2 Likes

What happens in Scala when the pattern match fails? (I could follow the link to try to find out, however I think this question’s answer should be in this thread.)

2 Likes

I've also been impressed with Python's parse as its own dual to format.

3 Likes

I believe most of this could work nicely using only macros, either using ? on failure with syntax like

destruct_string_throwing!("$greeting, $name" = "Hello, James");
println!(greeting);
...

Or inside an if:

if destruct_string!("$greeting, $name" = "Hello, James") {
    println!(greeting);
    ...
}

This second kind of binding-construct in possible in principle as demonstrated here.

I've been searching for that, and can't find any documentation of it other than the PR itself.

However, given that it's scala, there are 2 possibilities: some kind of value-based Result<_, _> style solution, or (more likely) an exception.

For Rust, I see 2 possibilities that are not on-the-face-of-it-ridiculous: a Result<_, _>, or a panic.

Personally, I think that a result of some kind would make more sense, as it allows the containing code to recover easily if the match fails. But that also implies that the syntax as presented in the examples won't work for Rust. Instead, it would seem better to integrate it into the pattern matching capabilities of match, which would include a _ => {} branch for match failures.

@rpjohnst I didn't know that existed. Cool!

Unfortunately those are not the same: the example on the playground defines an ident fragment, whereas your inline examples (and my proposal) define the identifier within the pattern string itself.

Note that my idea above to use macros without changing the Rust language works with match, too:

match Struct("Hello, James", /* ... */) {
    Struct::Variant(s, /* ... */) if destruct_string!("$greeting, $name" = s) => {
        println!(greeting);
        // ...
    }
    _ => { /* ... */ }
}

I think procedural macros can handle that, for example this crate seems to parse identifiers inside strings.

2 Likes

Without having looked at the code, I can say that macros handle that in the same way arbitrary code does: by actually parsing the string. Not that that's a bad thing, it needs to happen somewhere anyway.