header source
my icon
esplo.net
ぷるぷるした直方体
Cover Image for I created a tool to calculate the true rent using Rust's wasm

I created a tool to calculate the true rent using Rust's wasm

about17mins to read

I have released it here.

https://truerent.esplo.net/

3-line summary + image

  • I made a tool to calculate the true rent because it was a hassle
  • I wanted to try using Rust's wasm_bindgen, so I made it
  • If you're interested, please make a pull request here

Background of the tool

When renting a house, the rent is a major factor in the decision-making process.
In general, the rent and common area fees are added together to determine the monthly payment.

However, in reality, various other costs are incurred.
Representative examples include key money and brokerage fees.
These costs are significant, so they are particularly important.
Other unclear costs include insurance premiums, key exchange fees, and fixed cleaning fees.
I used to calculate these costs manually, but it was a hassle to do so for each room.

The purpose of this tool is to calculate the true rent by taking these costs into account and to help users find a better room.

Motivating Example

For example, let's consider a case where the rent is ¥50,000 and the common area fee is ¥2,000.
If nothing else is added, the monthly payment would be ¥52,000.
However, if key money of ¥1 month, brokerage fees of ¥1 month, fire insurance premiums of ¥10,000 at the time of contract, bicycle parking fees of ¥3,000 at the time of contract, and key exchange fees of ¥5,000 at the time of contract are added, the monthly payment would be ¥56,916.
This is ¥4,916 more than the original amount.

On the other hand, if the rent and common area fee are ¥55,000, and there are no key money or brokerage fees, the monthly payment would be ¥55,750, which is cheaper than the previous case.

As you can see, a room that seems cheaper at first glance may actually be more expensive due to other costs.
It's also a hassle to calculate these costs for each room.

How to use

I hope it's easy to understand by looking at the site...
Some functions are still under development. If you're interested, please make an issue or pull request.

  • Restore input values
  • Display the true rent as a graph when the residence period changes

Note that clicking on the i icon next to each item will display an explanatory comment.
If you have any questions, please make an issue or pull request.

Technical details

From here, I'll explain the internal workings of the tool.
You can find the source code on Github, so please take a look.
Also, the documentation for wasm_bindgen, which allows for easy interaction with the JS world, is here. Most of the information is written there.

Since this tool doesn't require a server, I decided to make it a static site.
I thought about writing it in React, but I wanted to try using Rust's WebAssembly output (wasm_bindgen) for the first time.

This web app is a classic form-based app.
The performance isn't critical, and the logic isn't complex, so it's not particularly suitable for wasm, but I was able to implement it.
However, interacting with the JS world was quite difficult.
It felt like I was using jQuery in the old days.

DOM manipulation

One of the difficult parts was manipulating the DOM.
The web_sys crate allows you to create elements and set attributes, but it's a low-level operation, so it's a hassle to write.
For example, to create a <div class="hoge">fuga</div> element and append it to a parent element, you would write the following code:

let elem = document.create_element("div")?;
elem.set_attribute("class", "hoge")?;
elem.set_inner_html("fuga");
parent.append_child(&elem)?;

It's like creating an element from scratch in JS.
React and other frameworks have JSX, which makes it easier, but unfortunately, Rust doesn't have such a framework yet.
I hope that someone will create a DSL like JSX for Rust in the future.

In the meantime, I created a function to make it a bit easier:

pub struct HtmlAttr<'a> {
    pub name: &'a str,
    pub value: &'a str,
}

pub fn make_tag(document: &Document, tag_name: &str,
                attr: Vec<HtmlAttr>, inner: Option<&str>,
                parent: Option<&Element>) -> Result<Element, JsValue> {
    let elem = document.create_element(tag_name)?;
    attr.into_iter().map(|a|
        elem.set_attribute(&a.name, &a.value)
    ).collect::<Result<_, _>>()?;
    if let Some(i) = inner {
        elem.set_inner_html(i);
    }
    if let Some(p) = parent {
        p.append_child(&elem)?;
    }
    return Ok(elem);
}

// Usage example
make_tag(&document, "span", vec![
    HtmlAttr { name: "class", value: "font-weight-bold" }
], Some(&self.text), Some(&label))?;

ID management

Another difficult part was ID management.
If you've used jQuery before, you know how painful it is.
It's possible to design a system to mitigate this, but it's still a hassle compared to React or Angular.

Event handlers

In JS, event handlers are easy to write, but in Rust, you need to be careful with ownership and lifetimes.

Specifically, you need to clone the necessary objects, create a closure, and register it as an event listener.
The official sample code shows an example of this:

    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            context.begin_path();
            context.move_to(event.offset_x() as f64, event.offset_y() as f64);
            pressed.set(true);
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }

https://rustwasm.github.io/docs/wasm-bindgen/examples/paint.html

It's interesting to see how JS's carefree coding style is replaced by Rust's strict ownership and lifetimes.

IDE support

The web_sys crate has some issues, and IntelliJ Rust and RLS don't provide completion.
This makes development difficult, and you need to compile and check for errors.
There are reports that using VSCode with rust-analyzer's Nightly version solves the issue, so please try it.

Testing

When interacting with the JS world, testing is a bit difficult.
However, if you can confine the logic to Rust, testing becomes much easier.
wasm_bindgen makes it easy to separate the layers, so it's possible to write tests that are confined to Rust.

Miscellaneous

  • I borrowed English words from the US Consumer Protection Agency's website, which is well-organized and has a friendly design.
  • I haven't written a large amount of code recently, so I feel like I'm not good at dividing modules.
  • I used Bootstrap for the first time in a while, and it was nostalgic.
  • I should run clippy before releasing.
    • It's natural to do so, especially for larger projects.
  • There are many things to do before releasing an OSS web app, such as setting up a Github repository, deploying to Netlify, setting up a custom domain, favicon, ogp, and Google Analytics.
    • It's a hassle, but it's necessary.

Conclusion

Primitive operations are handled by wasm_bindgen and web_sys.
The types are well-defined, so it's easier to write web apps than in JS.
However, for a classic web app like this, using React would be more efficient.

WebAssembly has many use cases, such as high-performance processing, WebGL, and WebRTC, where Rust's benefits can shine.
I'd like to try making such an app in the future.

And, please make the rent clear.

Postscript

Later, I found out that the yew framework provides a JSX-like DSL (thanks to @n_s_7-san), and Mozilla's blog has an article about Dodrio, which is a virtual DOM implementation using Rust and wasm.

https://github.com/yewstack/yew

https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/

https://github.com/fitzgen/dodrio

It's amazing to see that such frameworks and libraries already exist.
I'd like to try rewriting the app using these tools or creating a new one.

Share