How to convert closure to js_sys::Function? - rust

How to convert a local closure into a js_sys::Function?
I want to do something like this:
let canvas = document.get_element_by_id("canvas").unwrap();
let e: web_sys::HtmlElement = canvas.dyn_into().unwrap();
let f = || {};
e.set_onresize(Some(&f.into()));

I have found a solution here:
let f = Closure::wrap(Box::new(move || { /* whatever */}) as Box<dyn FnMut()>);
e.set_onresize(Some(f.as_ref().unchecked_ref()));
f.forget(); // It is not good practice, just for simplification!

An answer can be found on the wasm_bindgen::closure::Closure page, as well.
use wasm_bindgen::{closure::Closure, JsCast};
let cb = Closure::new(|| { ... });
let cb = cb.as_ref().unchecked_ref();

The following way to set a closure as a callback is slightly shorter than shown in Hossein Noroozpour's answer. It's adapted from this example:
let closure: Closure<dyn FnMut()> = Closure::new(move || {
// Do something here
});
your_html_element.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
Here's a more complete example, showing how to set the callback on a file picker. The callback accesses a JavaScript worker:
let document = window().unwrap().document().unwrap();
let worker = Worker::new("./worker.js").unwrap();
let file_picker_elem_id = "file_picker";
match get_input_element(file_picker_elem_id, &document) {
Some(file_picker) => {
// We need "move" to access the worker inside the closure
let closure: Closure<dyn FnMut()> = Closure::new(move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
});
file_picker.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
}
None => log_to_browser(format!(
"Couldn't get file picker. Used \"{file_picker_elem_id}\" as its ID"
)),
}
/// Gets an [[HtmlInputElement]] by its `elem_id`.
fn get_input_element(elem_id: &str, document: &Document) -> Option<HtmlInputElement> {
document
.get_element_by_id(elem_id)
.and_then(|elem| elem.dyn_ref::<HtmlInputElement>().map(ToOwned::to_owned))
}
fn log_to_browser(log_msg: String) {
console::log_1(&log_msg.into());
}
I tried simplifying the conversion of closures to js_sys::Functions:
pub fn to_js_func<F>(closure: F) -> Option<&'static js_sys::Function>
where
F: IntoWasmClosure<dyn FnMut()> + 'static,
{
let big_closure: Closure<dyn FnMut()> = Closure::new(closure);
let func: &js_sys::Function = big_closure.as_ref().unchecked_ref();
big_closure.forget();
Some(func)
}
But this doesn't work since the function owns the created closure. Also, to use the type js_sys::Function, I needed to add the js-sys dependency to Cargo.toml. If anybody knows a way around this, please let us know.
A less universal helper function that works though:
fn set_on_input<F>(input_elem: &HtmlInputElement, closure: F)
where
F: IntoWasmClosure<dyn FnMut()> + 'static,
{
let big_closure: Closure<dyn FnMut()> = Closure::new(closure);
input_elem.set_oninput(Some(big_closure.as_ref().unchecked_ref()));
big_closure.forget();
}
This allows setting a Rust closure as the callback on the HTML element:
let closure = move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
};
set_on_input(&file_picker, closure);
Instead of a closure, you can also set a function as the callback:
set_on_input(&file_picker, test_callback);
Where test_callback is just a regular Rust function:
fn test_callback() {
log_to_browser("test_callback 🥳".into());
}

Related

Clean way to get Option::unwrap_or_else behaviour with an Option<&T>

I would like to know if there's any elegant solution for getting code/behaviour similar to unwrap_or_else on an Option<&T>. My use case is to pass an optional reference to a function and if it's not used then create a default value of the same type to use. Here's a boiled-down version of my code:
#[derive(Debug)]
struct ExpensiveUnclonableThing {}
fn make_the_thing() -> ExpensiveUnclonableThing {
// making the thing is slow
// ...
ExpensiveUnclonableThing {}
}
fn use_the_thing(thing_ref: &ExpensiveUnclonableThing) {
dbg!(thing_ref);
}
fn use_or_default(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
enum MaybeDefaultedRef<'a> {
Passed(&'a ExpensiveUnclonableThing),
Defaulted(ExpensiveUnclonableThing),
}
let thing_md = match thing_ref_opt {
Some(thing_ref) => MaybeDefaultedRef::Passed(thing_ref),
None => MaybeDefaultedRef::Defaulted(make_the_thing()),
};
let thing_ref = match &thing_md {
MaybeDefaultedRef::Passed(thing) => thing,
MaybeDefaultedRef::Defaulted(thing) => thing,
};
use_the_thing(thing_ref);
}
fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
use_the_thing(thing_ref);
}
fn main() {
let thing = make_the_thing();
use_or_default(Some(&thing));
use_or_default(None);
use_or_default_nicer(Some(&thing));
use_or_default_nicer(None);
}
The thing is dropped right away when the unwrap_or_else closure ends, so I of course get an error stating that I can't do that:
error[E0515]: cannot return reference to temporary value
--> src/main.rs:31:53
|
31 | let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
| ^----------------
| ||
| |temporary value created here
| returns a reference to data owned by the current function
What is the 'idiomatic Rust' way of writing use_or_default? Is there a way I can get it to look similar to how use_or_default_nicer is implemented other than by creating a generic MaybeDefaultedRef<T> type + with some convenience methods? I am open to refactoring the whole thing if there's a better way.
You can write something like this:
fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
let mut maybe = None;
let thing_ref = thing_ref_opt.unwrap_or_else(
|| maybe.insert(make_the_thing())
);
use_the_thing(thing_ref);
}
That is, you can keep the value itself outside of the function and then assign to it if necessary. Unfortunately, an unitialized value cannot be capture by a lambda so you have to make the variable Option<ExpensiveUnclonableThing> and initialize with None.
But in a real code of mine, I had the same issue and I wrote a manual match:
fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
let maybe;
let thing_ref = match thing_ref_opt {
Some(x) => x,
None => {
maybe = make_the_thing();
&maybe
}
};
use_the_thing(thing_ref);
}
In my opinion this is nicer even if a bit longer, because you don't need the Option<_> or the maybe variable being mutable` or the fake initialization.
Some people feel a bit of a defeat when they match on an Option, and think it is un-idiomatic, but I don't particularly care.
A plain old if/else would do also, no need to convolute things:
fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
if let Some(e) = thing_ref_opt {
use_the_thing(e);
} else {
let e = make_the_thing();
use_the_thing(&e);
}
}
Playground

How do I reduce multiple if branches to one in order to avoid duplicating similar logic?

A database API gives us the option to build a Filter object that will be passed onto a Query.
It offers a fluent API to build such Filter:
filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3)
The topics are pre-organised into a Vec<Option<String>>, which we can pass onto the filters obj, using the helper function:
fn add_topics(mut f: &Filter, topics: Vec<String>) {
if let Some(topic0) = topics.get(0) {
f = &f.topic0(topic0);
}
if let Some(topic1) = topics.get(1) {
f = &f.topic1(topic1);
}
if let Some(topic2) = topics.get(2) {
f = &f.topic2(topic2);
}
if let Some(topic3) = topics.get(3) {
f = &f.topic3(topic3);
}
}
Unfortunately, the db API doesn't offer a .topics() method that accepts a Vec<_>.
Still, is there any way to avoid duplication of the logic?
E.g. I'm proficient with JS/TS, in which the above can be written as:
const addTopics = (filter: Filter, topics: string[]) {
for (let i = 0; i < topics.length; ++i) {
if (topics[i]) {
filter = filter['topic' + i](topics[i]);
}
}
}
Rust, as a typed language, doesn't allow this as far as I know.
Is there any other way?
By a minimal reproducible example, Herohtar means something like:
struct Filter {}
impl Filter {
fn topic0(&self, topic: &str) -> &Self {
self
}
fn topic1(&self, topic: &str) -> &Self {
self
}
fn topic2(&self, topic: &str) -> &Self {
self
}
fn topic3(&self, topic: &str) -> &Self {
self
}
}
fn main() {
let topic0 = "";
let topic1 = "";
let topic2 = "";
let topic3 = "";
let filter = Filter {};
let f = filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3);
}
This abstracts away all of the details, while giving a clear example of what you mean. (If what I've written isn't what you mean, then that's the point of writing a minimal example.)
To build the function you're looking for, you need a way to map what method you want to call to each location. That's fairly straightforward, although it requires a somewhat advanced type:
fn add_topics(mut f: &Filter, topics: Vec<Option<String>>) {
// Fancy type because of the borrows that need a lifetime.
// It's just the signature for a method call. The `for` allows
// adding a lifetime.
type TopicAssigner = for<'a> fn(&'a Filter, &str) -> &'a Filter;
// Now, match up the methods in the order of the Vector
let assign_topic: Vec<TopicAssigner> = vec![
Filter::topic0,
Filter::topic1,
Filter::topic2,
Filter::topic3,
];
// And zip it with what you're passed
for (topic, assigner) in zip(topics, assign_topic) {
if let Some(topic) = topic {
f = (assigner)(f, &topic);
}
}
}
Note that this has a problem if topics has more values than are expected, so it would be nice to force it into a 4-element array instead:
fn add_topics(mut f: &Filter, topics: [Option<String>; 4]) {
But perhaps that's inconvenient for other reasons.

Struct property accessable from method but not from outside

I'm trying to build a basic web crawler in Rust, which I'm trying to port to html5ever. As of right now, I have a function with a struct inside that is supposed to return a Vec<String>. It gets this Vec from the struct in the return statement. Why does it always return an empty vector? (Does it have anything to do with the lifetime parameters?)
fn find_urls_in_html<'a>(
original_url: &Url,
raw_html: String,
fetched_cache: &Vec<String>,
) -> Vec<String> {
#[derive(Clone)]
struct Sink<'a> {
original_url: &'a Url,
returned_vec: Vec<String>,
fetched_cache: &'a Vec<String>,
}
impl<'a> TokenSink for Sink<'a> {
type Handle = ();
fn process_token(&mut self, token: Token, _line_number: u64) -> TokenSinkResult<()> {
trace!("token {:?}", token);
match token {
TagToken(tag) => {
if tag.kind == StartTag && tag.attrs.len() != 0 {
let _attribute_name = get_attribute_for_elem(&tag.name);
if _attribute_name == None {
return TokenSinkResult::Continue;
}
let attribute_name = _attribute_name.unwrap();
for attribute in &tag.attrs {
if &attribute.name.local != attribute_name {
continue;
}
trace!("element {:?} found", tag);
add_urls_to_vec(
repair_suggested_url(
self.original_url,
(&attribute.name.local, &attribute.value),
),
&mut self.returned_vec,
&self.fetched_cache,
);
}
}
}
ParseError(error) => {
warn!("error parsing html for {}: {:?}", self.original_url, error);
}
_ => {}
}
return TokenSinkResult::Continue;
}
}
let html = Sink {
original_url: original_url,
returned_vec: Vec::new(),
fetched_cache: fetched_cache,
};
let mut byte_tendril = ByteTendril::new();
{
let tendril_push_result = byte_tendril.try_push_bytes(&raw_html.into_bytes());
if tendril_push_result.is_err() {
warn!("error pushing bytes to tendril: {:?}", tendril_push_result);
return Vec::new();
}
}
let mut queue = BufferQueue::new();
queue.push_back(byte_tendril.try_reinterpret().unwrap());
let mut tok = Tokenizer::new(html.clone(), std::default::Default::default()); // default default! default?
let feed = tok.feed(&mut queue);
return html.returned_vec;
}
The output ends with no warning (and a panic, caused by another function due to this being empty). Can anyone help me figure out what's going on?
Thanks in advance.
When I initialize the Tokenizer, I use:
let mut tok = Tokenizer::new(html.clone(), std::default::Default::default());
The problem is that I'm telling the Tokenizer to use html.clone() instead of html. As such, it is writing returned_vec to the cloned object, not html. Changing a few things, such as using a variable with mutable references, fixes this problem.

Closure may outlive the current function

I am just starting to learn Rust. For this purpose I am rewriting my C++ project in Rust, but the biggest problems are lifetimes of closures and such.
I created a absolute minimal scenario of my problem seen here and below:
use std::sync::Arc;
use std::cell::{RefCell, Cell};
struct Context {
handler: RefCell<Option<Arc<Handler>>>,
}
impl Context {
pub fn new() -> Arc<Context> {
let context = Arc::new(Context{
handler: RefCell::new(None),
});
let handler = Handler::new(context.clone());
(*context.handler.borrow_mut()) = Some(handler);
context
}
pub fn get_handler(&self) -> Arc<Handler> {
self.handler.borrow().as_ref().unwrap().clone()
}
}
struct Handler {
context: Arc<Context>,
clickables: RefCell<Vec<Arc<Clickable>>>,
}
impl Handler {
pub fn new(context: Arc<Context>) -> Arc<Handler> {
Arc::new(Handler{
context: context,
clickables: RefCell::new(Vec::new()),
})
}
pub fn add_clickable(&self, clickable: Arc<Clickable>) {
self.clickables.borrow_mut().push(clickable);
}
pub fn remove_clickable(&self, clickable: Arc<Clickable>) {
// remove stuff ...
}
}
struct Clickable {
context: Arc<Context>,
callback: RefCell<Option<Box<Fn()>>>,
}
impl Clickable {
pub fn new(context: Arc<Context>) -> Arc<Clickable> {
let clickable = Arc::new(Clickable{
context: context.clone(),
callback: RefCell::new(None),
});
context.get_handler().add_clickable(clickable.clone());
clickable
}
pub fn remove(clickable: Arc<Clickable>) {
clickable.context.get_handler().remove_clickable(clickable);
}
pub fn set_callback(&self, callback: Option<Box<Fn()>>) {
(*self.callback.borrow_mut()) = callback;
}
pub fn click(&self) {
match *self.callback.borrow() {
Some(ref callback) => (callback)(),
None => (),
}
}
}
struct Button {
context: Arc<Context>,
clickable: Arc<Clickable>,
}
impl Button {
pub fn new(context: Arc<Context>) -> Arc<Button> {
let clickable = Clickable::new(context.clone());
let button = Arc::new(Button{
context: context,
clickable: clickable.clone(),
});
let tmp_callback = Box::new(|| {
button.do_stuff();
});
clickable.set_callback(Some(tmp_callback));
button
}
pub fn do_stuff(&self) {
// doing crazy stuff
let mut i = 0;
for j in 0..100 {
i = j*i;
}
}
pub fn click(&self) {
self.clickable.click();
}
}
impl Drop for Button {
fn drop(&mut self) {
Clickable::remove(self.clickable.clone());
}
}
fn main() {
let context = Context::new();
let button = Button::new(context.clone());
button.click();
}
I just don't know how to pass references in closures.
Another ugly thing is that my Handler and my Context need each other. Is there a nicer way to to create this dependency?
Going off your initial code
pub fn new(context: Arc<Context>) -> Arc<Button> {
let clickable = Clickable::new(context.clone());
let button = Arc::new(Button{
context: context,
clickable: clickable.clone(),
});
let tmp_callback = Box::new(|| {
button.do_stuff();
});
clickable.set_callback(Some(tmp_callback));
button
}
First off, let's note the error you're getting
error[E0373]: closure may outlive the current function, but it borrows `button`, which is owned by the current function
--> src/main.rs:101:37
|
101 | let tmp_callback = Box::new(|| {
| ^^ may outlive borrowed value `button`
102 | button.do_stuff();
| ------ `button` is borrowed here
|
help: to force the closure to take ownership of `button` (and any other referenced variables), use the `move` keyword, as shown:
| let tmp_callback = Box::new(move || {
Noting the help block at the bottom, you need to use a move closure, because when the new function ends, the button variable on the stack will go out of scope. The only way to avoid that is to move ownership of it to the callback itself. Thus you'd change
let tmp_callback = Box::new(|| {
to
let tmp_callback = Box::new(move || {
Now, you'd get a second error:
error[E0382]: use of moved value: `button`
--> src/main.rs:107:9
|
102 | let tmp_callback = Box::new(move || {
| ------- value moved (into closure) here
...
107 | button
| ^^^^^^ value used here after move
|
= note: move occurs because `button` has type `std::sync::Arc<Button>`, which does not implement the `Copy` trait
And the error here may be a little clearer. You're trying to move ownership of the button value into the callback closure, but you also use it inside the body of the new function when you return it, and you can't have two different things trying to own the value.
The solution to that is hopefully what you'd guess. You have to make a copy that you can take ownership of. You'll want to then change
let tmp_callback = Box::new(move || {
button.do_stuff();
to
let button_clone = button.clone();
let tmp_callback = Box::new(move || {
button_clone.do_stuff();
Now you've created a new Button object, and returned an Arc for the object itself, while also giving ownership of a second Arc to the callback itself.
Update
Given your comment, there is indeed an issue here of cyclic dependencies, since your Clickable object holds ownership of a reference to Button, while Button holds ownership of a reference to Clickable. The easiest way to fix this here would be to update that code a third time, from
let button_clone = button.clone();
let tmp_callback = Box::new(move || {
button_clone.do_stuff();
to
let button_weak = Arc::downgrade(&button);
let tmp_callback = Box::new(move || {
if let Some(button) = button_weak.upgrade() {
button.do_stuff();
}
});
so the Clickable will only hold a weak reference to the Button, and if the Button is no longer referenced, the callback will be a no-op.
You'd also probably want to consider making clickables a list of Weak references instead of strong references, so you can remove items from it when the item they reference is removed.

How to setup routes in Rouille by iterating a vector of routes

The Rouille hello world example shows how to use the router! macro for a fixed set of routes.
The following example code illustrates the need to be able to "bootstrap" routes from a database or from pluggable code - which I've currently been able to do with the Iron web framework:
pub struct Route {
pub http_method: String,
pub url_path: String,
pub callback_func: fn(_: &mut Request) -> IronResult<Response>,
}
impl Route {
pub fn new(m: String, u: String, f: fn(_: &mut Request) -> IronResult<Response>) -> Route {
Route {
http_method: m,
url_path: u,
callback_func: f,
}
}
}
fn main() {
// router is an Iron middleware
let mut router = Router::new();
// prepare routes for bootstrapping into the Iron router
let r1 = Route::new("get".to_string(), "/*".to_string(), my_callback_func);
let r2 = Route::new("get".to_string(), "/".to_string(), my_callback_func);
let mut routes = Vec::new();
routes.push(r1);
routes.push(r2);
for route in routes.iter_mut() {
if route.http_method == "get" {
// passes each route to the Iron router
router.get(&route.url_path, (&*route).callback_func);
} // else if, put, post, delete...
}
Iron::new(router).http("localhost:3000").unwrap();
}
fn my_callback_func(_: &mut Request) -> IronResult<Response> {
//...
}
(Playground)
Although I'm reading up on macros in Rust, I do not have a good enough understanding of Rouille's router! macro, Rust or macros in general, to figure out how to achieve the equivalent with Rouille.
If you examine the main source of the router macro, it's long-ish but not supremely complicated:
($request:expr, $(($method:ident) ($($pat:tt)+) => $value:block,)* _ => $def:expr) => {
{
let request = &$request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
let mut ret = None;
$({
if ret.is_none() && request.method() == stringify!($method) {
ret = router!(__check_pattern request_url $value $($pat)+);
}
})+
if let Some(ret) = ret {
ret
} else {
$def
}
}
};
In words, it takes a request, zero-or-more patterns to match, and a default. It gets ahold of the URL, then dispatches to the other arms of the macro to see if the URL matches the path and does some recursive trickery to define some variables with components of the path. Whichever arm matches first will set the return value, and if nothing matches, the default will be used.
Unfortunately, the macro expects idents for the methods and the paths, so basically you cannot use it with expressions. This means we can't pass in variables or literals like "foo". This makes it very difficult for you.
So, we do what all good programmers do: copy and paste the code. Lifting chunks out of the macro and repurpose them:
#[macro_use]
extern crate rouille;
use rouille::Request;
use rouille::Response;
struct Route(&'static str, &'static str, fn(&Request) -> Response);
fn main() {
let routes = [
Route("GET", "/one", do_one),
Route("GET", "/two", do_two),
];
rouille::start_server("0.0.0.0:9080", move |request| {
let mut result = None;
let request = &request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
for &Route(method, path, f) in &routes {
if result.is_none() {
// This checking of the path is terrible, limited, and hacky
if request.method() == method && request_url.ends_with(path) {
result = Some(f(request));
}
}
}
result.unwrap_or_else(|| Response::text("Default!"))
});
}
fn do_one(_: &Request) -> Response {
Response::text("hello world one")
}
fn do_two(_: &Request) -> Response {
Response::text("hello world two")
}
This runs the various handlers for /one, /two and everything else.
I'm no expert in Rouille, in fact I've never used it before today, but it certainly seems like you are trying to use it for something beyond what it is currently designed for. Perhaps this is deliberate and the authors are attempting to present a very opinionated tool. Perhaps it is accidental and they haven't thought of this use case. Perhaps it is temporary, and they just haven't gotten around to it.
Either way, I'd suggest asking the authors. If it's not an intended use-case, they can update the project docs to clearly state so. Otherwise they might create issues to implement the feature, and you could be instrumental in helping design it.

Resources