Syntax extensions and regular expressions for Rust
blog.burntsushi.net
blog.burntsushi.net
I haven't had the pleasure of using a language with a good macro facility. What other sorts of applications do macros have, aside from this and type safe println are there? Could one make a json or XML parsing library with this technique? Or a driver for a database? How does one go about acquiring an intuition for when to use or not use them?
> How does one go about acquiring an intuition for when to use or not use them?
Truthfully, I don't know. In this particular case, the benefits seem really clear: we get safety and performance increases.
But here's a caveat: the particular reason why this works well for regexes is because the common use case is to write the string literal corresponding to the regex into your program. This is what allows the macro facilities to kick in. For example, if the regex is derived from user input at runtime, then the `regex!` macro can't be used.
i.e., you can't do this:
let restr = "a*";
let re = regex!(restr);
The `regex!` macro must accept a string literal (or another macro invocation that produces a string literal).I did find an interesting slide deck from the scala universe that mentions something like F# type providers (among other things) as possible applications.
http://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosG...
(Edit: after a quick check this use case seems to be already covered by #[deriving(Encodable, Decodable)] in rust.)
The Nimrod standard library has many examples of what macros can do. For example, I have been working on an async macro (now part of the latest Nimrod release) which allows asynchronous function calls to be made in a similar fashion to ordinary blocking function calls. It's very similar to C#'s await. The way it works is that it transforms functions marked as 'async' into an iterator, while at the same time transforming any occurrences of 'await' into a yield.
There will be a talk at this year's D conference on D's implementation of regular expressions:
I like how the rust community is offering so many good examples of real utility for the language. Some languages are a little heavy on theory but have trouble connecting with potential users; and other languages are popular but have a lot of problems due to fundamental weaknesses.
Rust, and its community, seems to have a good balance. Good theoretical foundation, but a lot of people always working to show some practical reason to care about the theory.
Firstly, it's my understanding that JITing pays a runtime cost. That's not the case with Rust compiled regexes. (Admittedly, this cost is likely negligible.)
Secondly, wouldn't PyPy JIT the general matching algorithm? This seems like a substantively different process than doing code generation specifically for a regex whose structure you know at compile time. (Whether I'm currently taking advantage of any optimizations that PyPy's JIT isn't is of course a different story.)
JITting a regex isn't really the same thing as your Rust native regexes; for example, if somebody wrote a regex library with a JIT and linked it to a C program, I woudln't count it. However, in PyPy's case regexes are exactly as native as the rest of the codebase rather than being a nested VM, so in that sense they're 'native'.
(You have to scroll down to the code block sections to see the numbers).
Rust beats Go pretty soundly (on this specific benchmark), but C beats Rust pretty soundly.
Why is C so much faster?
Because processors are made with C in mind, as it's the lingua franca, and so they end up being optimized for it.
NOTE: Rust is supposed to have zero cost abstractions. I think this is more to lack of optimization on regexp part, than some fault of language.
No, Rust exposes the C memory model pretty directly, and it uses the backend of an industrial-strength C++ compiler (clang/LLVM). The difference here is algorithmic.
(Rust does have some codegen bugs that affect performance, however, though I suspect they won't affect this code.)
I also have some more specific benchmarks against Go: https://github.com/BurntSushi/regexp/tree/master/benchmark
(For any curious readers out there, the benchmark is multithreaded in Rust[1], with a particularly nice use of futures.)
> Rust beats Go pretty soundly (on this specific benchmark), but C beats Rust pretty soundly.
I believe the dynamic regexes also beat Go on this specific benchmark, but the optimizing compiler is likely to blame there. (And to be clear, Go still beats Rust native regexes in some benchmarks, primarily because it has more clever optimizations. It was written by Russ Cox, after all!)
> Why is C so much faster?
The C program is pretty elaborate, but here are my guesses.
* There are many ways to implement regex matching. For example, RE/C++ uses both a very very quick DFA and a NFA simulation. My library only has the (slower) NFA simulation. I don't know what Tcl uses for an implementation strategy, but whatever it is, it's likely a factor.
* If string replacement is done in place, then it's going to avoid a good chunk of allocation. (In this particular benchmark, it would be perfectly legal to do string replacement in place.)
I'm sure there are other things I'm missing, but these seem like good candidates for a good chunk of the difference.
[1] - https://github.com/BurntSushi/regexp/blob/master/benchmark/r...
(Creating a regex from untrusted data still needs a bit of work, but is fixable!)