#[js(func)]
Use #[js(func)]
for calling JavaScript functions.
use ferrosaur::js;
#[path = "../../../crates/ferrosaur/tests/fixture/mod.rs"]
mod fixture;
#[js(value)]
struct Console;
#[js(interface)]
impl Console {
#[js(func)]
fn log(&self, message: serde<&str>) {}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let rt = &mut fixture::deno()?;
// let rt: &mut JsRuntime;
let console: Console = fixture::eval_value("({ log: () => {} })", rt)?;
// let console: Console;
console.log("🦀 + 🦕", rt)?;
Ok(())
}
// Expressed in TypeScript:
interface Console {
log(message: string): void;
}
declare let console: Console;
console.log("🦀 + 🦕");
The generated function has the signature:
Argument types must implement either ToV8
(the default) or Serialize
(if written
as serde<T>
). The return type must implement either FromV8
or
DeserializeOwned
.
note
See Specifying types for more info on how you can specify types when using this crate.
Implicitly, the function name is the Rust function name
converted to camelCase, but you can override this using the
name
or Symbol
option.
async
functions
use ferrosaur::js;
#[path = "../../../crates/ferrosaur/tests/fixture/mod.rs"]
mod fixture;
use fixture::items::global::Global;
#[js(interface)]
impl Global {
#[js(prop(name(Promise)))]
fn promise_constructor(&self) -> PromiseConstructor {}
}
#[js(value)]
struct PromiseConstructor;
#[js(interface)]
impl PromiseConstructor {
#[js(func)]
async fn resolve(&self, value: serde<u64>) -> serde<u64> {}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let rt = &mut fixture::deno()?;
// let rt: &mut JsRuntime;
let global = Global::new(rt);
#[allow(non_snake_case)]
let Promise = global.promise_constructor(rt)?;
// let Promise: PromiseConstructor;
assert_eq!(Promise.resolve(42, rt).await?, 42);
Ok(())
}
// Expressed in TypeScript:
interface PromiseConstructor {
resolve(value: number): Promise<number>;
}
declare let Promise: PromiseConstructor;
assert((await Promise.resolve(42)) === 42);
The generated function will be an async fn
. The returned Future
will be ready once
the underlying JS value fulfills.
Internally, this calls JsRuntime::with_event_loop_promise
, which means you don't
need to drive the event loop separately.
this
argument
By default, the JS function will receive the object from which the function is accessed
(i.e. &self
) as its this
value. Expressed in TypeScript, the way your function is
invoked is roughly:
interface Foo {
bar: () => void;
}
declare const foo: Foo;
const bar = foo.bar;
bar.call(foo);
Alternatively, you can explicitly declare the type of this
using the second argument:
this: undefined
use ferrosaur::js;
#[js(value)]
struct Foo;
#[js(interface)]
impl Foo {
#[js(func)]
fn bar(&self, this: undefined) {}
}
// Expressed in TypeScript:
const bar = foo.bar;
bar.call(undefined);
The JS function will receive a this
value of undefined
when called.
The resulting Rust function will not have a this
argument.
this: [SomeType]
use ferrosaur::js;
#[js(value)]
struct Foo;
#[js(interface)]
impl Foo {
#[js(func)]
fn bar(&self, this: Baz) {}
}
#[js(value)]
struct Baz;
// Expressed in TypeScript:
interface Foo {
bar: (this: Baz) => void;
}
const bar = foo.bar;
declare const baz: Baz;
bar.call(baz);
The resulting Rust function will have an explicit this
argument, for which you will
supply a value at call time; the argument will be subject to the same
type conversion rules as other arguments.
Spread arguments
To indicate an argument should be flattened using the spread syntax at call time, prefix
the argument name with ..
(2 dots):
use deno_core::v8;
use ferrosaur::js;
#[path = "../../../crates/ferrosaur/tests/fixture/mod.rs"]
mod fixture;
use fixture::items::global::Global;
#[js(value)]
struct Console;
#[js(interface)]
impl Console {
#[js(func(name(log)))]
pub fn log(&self, ..values: Vec<String>) {}
// ^
}
// let rt: &mut JsRuntime;
let rt = &mut fixture::deno()?;
// let console: Console;
let console: Console = fixture::eval_value("({ log: () => {} })", rt)?;
console.log(vec!["🦀".into(), "🦕".into()], rt)?;
Ok::<_, anyhow::Error>(())
// Expressed in TypeScript:
interface Console {
log: (...values: string[]) => void;
}
declare const console: Console;
console.log(...["🦀", "🦕"]);
On the Rust side, a spread argument of type A
must implement Iterator<Item = T>
,
where T
must implement either ToV8
(the default) or Serialize
(if written as
serde<T>
). When calling the function, pass the argument using normal syntax.
note
See Specifying types for more info on how you can specify types when using this crate.
tip
The syntax ..args: A
is abusing the range pattern syntax, which is
syntactically valid in function arguments.
Option name = "..."
Use the specified string as key when accessing the function. This has the same usage as
js(prop(name))
.
You can also write name(propertyKey)
if the key is identifier-like.
use ferrosaur::js;
#[js(value)]
struct Date;
#[js(interface)]
impl Date {
#[js(func(name(toISOString)))]
fn to_iso_string(&self) -> serde<String> {}
}
Option Symbol(...)
Use the specified well-known Symbol when accessing the function.
This has the same usage as js(prop(Symbol))
.
use ferrosaur::js;
use deno_core::serde_json;
#[js(value)]
struct Date;
#[js(interface)]
impl Date {
#[js(func(Symbol(toPrimitive)))]
fn to_primitive(&self, hint: serde<&str>) -> serde<serde_json::Value> {}
}