Working JavaScript in Rust with Deno

Exploring Deno’s JavaScript Runtime in a Proof-of-Idea Rust Software for Filtering Textual content with JS Expressions
Written by Austin Poor
Printed 2023-05-03

After I noticed that Deno has open supply Rust crates for operating JavaScript code in a Rust utility I needed to present it a strive for myself, so I created a fundamental, proof-of-concept venture referred to as js-in-rs
to get a really feel for utilizing Deno’s crates in a Rust program — particularly the deno_core
crate.
What Does it Do?
The objective of js-in-rs
is to be a CLI, written in Rust, for filtering recordsdata utilizing JavaScript expressions. It’s just like the software grep
besides the place grep makes use of common expressions, js-in-rs
makes use of JavaScript.
The js-in-rs
CLI takes two arguments — a path to an enter file that shall be filtered and a JavaScript expression the place the the worth line
shall be set to the worth of every line within the enter file and the expression’s truthiness will decide if the road needs to be printed.
Right here’s an instance of what it would appear like in apply:
js-in-rs instance.txt 'line.size > 5'
If the contents of the file instance.txt
seemed like this:
A
BBBBBB
CCC
DDDDD
EEEEEEE
The output of our command can be:
BBBBBB
EEEEEEE
Solely the traces of the file with greater than 5 characters can be printed.
This can be a fairly easy instance of a JavaScript filter however because of the versitility of JavaScript, this software is ready to signify far more complicated filtering logic. In lots of circumstances filters can be very onerous if not not possible to specific utilizing common expressions. Not solely is JavaScript extra readable but it surely can be extra expressive.
Say you had the next filter circumstances:
- If the road is
foo
, print it - If the road has 10-20 characters, excluding main or trailing areas, print it
- In any other case, don’t print it
Now say we have now the next enter file, pattern.txt
:
foo
foo
brief
it is a lengthy line
this line is simply too lengthy, although
We may use the next grep
command (I’m utilizing the -P
flag for Perl-compatable regex expressions):
grep -P '^(foo|s*.{10,20}?s*)$' pattern.txt
And we might appropriately get the next output:
foo
it is a lengthy line
Even on this instance, we’re operating into problems with readability for the common expression and, because the discerning reader might have observed, this regex sample doesn’t account for all edge circumstances (eg trailing areas).
In contrast, utilizing js-in-rs
we may write the command as follows:
js-in-rs pattern.txt
'line == "foo" || (line.trim().size >= 10 && line.trim().size <= 20)'
I’d argue that that is a lot simpler to learn than the revious regex sample however even nonetheless, we may take it a step additional and break up it out into a number of traces — together with variable assignments and feedback:
js-in-rs pattern.txt "$(cat <<EOF
{
// Is the road "foo"?
if (line === "foo") return true;
// Is the road's size (excluding lead-/trail-ing ws) in [10,20]
const trimLine = line.trim();
if (trimLine.size >= 10 && trimLine.size <= 20) {
return true;
}
// In any other case, do not print...
return false;
}
EOF
)"
Certain sufficient this provides us the identical end result:
foo
it is a lengthy line
Now should you’re trying again by way of your bash historical past after a month or two, you’ll have a a lot simpler time remembering what that command does.
What should you needed to solely present traces the place a minimum of 50% of the characters within the line are uppercase? I do not know how to do this utilizing common expressions. Right here’s what the JavaScript filter may appear like utilizing js-in-rs
:
line.size > 0 && (
Array.from(line)
.map(c => c === c.toUpperCase())
.scale back((a, b) => a + b)
/ line.size) > 0.5
Because of JavaScript and the Deno runtime, js-in-rs
is ready to be extra expressive and extra versitile than grep and but easier than writing out a full script in JavaScript and operating it your self with Node or Deno.
Present Me the Rust!
Utilizing the Deno repository’s examples as reference I used to be capable of get a easy instance up and operating with out a lot of a problem.
After parsing the command line arguments and studying within the supply file to be filtered, this system creates a single occasion of the deno_core::JsRuntime
that may then be reused all through the appliance.
let mut runtime = JsRuntime::new(
RuntimeOptions::default(),
);
It then iterates by way of the supply file, line-by-line, formatting the filter right into a JavaScript expression that defines an nameless operate and calls it utilizing the supply file’s line because the argument (see the above part’s clarification).
That expression is then evaluated utilizing the JS runtime and the result’s captured.
let end result = runtime.execute_script(
"matcher.js",
js_matcher.into(),
);
The end result returned can then be deserialized as a serde_json::Worth
enum which is anticipated to be a boolean worth.
let scope = &mut runtime.handle_scope();
let native = v8::Native::new(scope, international);
let deserialized_value = serde_v8::from_v8::<serde_json::Worth>(scope, native);
Assuming a boolean kind is returned, its worth will decide if the road needs to be printed.
match worth {
serde_json::Worth::Bool(b) => {
if b {
println!("{}", line);
}
},
_ => return Err(Error::msg(format!(
"JS matcher should return a boolean worth!",
))),
}
The deno_core
crate does a very good job of passing alongside error messages which might are available fairly useful. For instance, say you accidentially add a semicolon in the midst of your filter expression:
js-in-rs src/primary.rs
'line.trim().size > 20 &;& line.trim().size < 50'
You’ll get a useful error message like the next:
Error: Eval error: Uncaught SyntaxError: Sudden token ';'
at matcher.js:1:39
Admitidly the road numbers might not match up fully, given the truth that the Rust utility inserts the filter into a bigger expression and error references the file matcher.js
which isn’t actual, however it’s a adequate start line to debug the difficulty.
So…can I "rm /usr/bin/grep"
?
By now you’re most likely all-in on js-in-rs
and able to delete grep
solely however earlier than you do, do not forget that that is only a proof of idea. It’s very mild on options, mild on testing, and there’s room for enchancment on the efficiency.
Even when this venture have been to proceed on so as to add options, add exams, and increase the efficiency, chances are you’ll be higher off utilizing a software written solely in Rust or solely in JavaScript.
Rathar than embedding a JavaScript runtime in Rust, the entire software might be written in JavaScript and compiled into a self-contained executable using Deno. Although, with that mentioned, the compiled model of js-in-rs
finally ends up being about half the dimensions of the pure-JS Deno-compiled model — the discharge construct of js-in-rs
is 54 MB
, grep
is 179 KB
, and a fundamental good day world JS utility compiled utilizing Deno is 103 MB
.
Takeaways, Suggestions & Conclusions
Working with Deno in rust was a number of enjoyable. The documentation was a bit sparce however what they do have, mixed with the examples they supply, was sufficient to get me up and operating to create this small PoC that I’m calling js-in-rs
.
Whereas I don’t count on js-in-rs
to unseat grep
because the go-to command line software for filtering textual content — and don’t even actually plan to proceed growing it — the expertise was greater than sufficient to pique my curiosity and get me fascinated with every kind of different potential functions for operating JavaScript in Rust utilizing Dino.
Rust is quick and secure however a bit slower to put in writing and with a bit steaper of a studying curve whereas JavaScript is well-known language that’s quick to put in writing however that usually runs extra slowly — the 2 languages can match collectively synergistically.
Listed below are just a few potential functions that would profit from operating JavaScript in Rust:
- Internet-Servers/APIs with the ability and efficiency of Rust that may be custom-made utilizing JavaScript
- Knowledge pipelines (eg Apache Airflow) the place the orchestration is dealt with by Rust however the high-level enterprise logic is outlined utilizing JavaScript
- Consumer Outlined Capabilities (UDFs) for databases the place Rust can run JS features in a secure sandbox
The Deno runtime additionally has an fascinating strategy to permissions the place it permits customers to activate/off runtime options like community entry, file system entry, FFI entry, atmosphere variable entry, and so on. to assist stop nefarious code from accessing sources it shouldn’t. Whereas I didn’t get an opportunity to discover it on this utility, it’s on my checklist to strive sooner or later.
In case you discovered this fascinating I extremely suggest attempting it out your self. Mess around with Deno’s Rust crates, create your individual functions that combine JavaScript, and should you can, doc it to assist construct the collective knowlege base!