
I Created a Tool to Calculate Actual Rent Using Rust's wasm
It's published here.
3-line summary + image
- Made a tool because calculating actual rent is troublesome
- Tried using Rust's wasm_bindgen because I wanted to touch it somehow
- If you're knowledgeable, please submit a pull request
Background of the Tool
Rent is a major criterion when deciding to rent a house.
Generally, the monthly payment amount is considered to be the sum of the amount displayed as "rent" plus the amount displayed as "common service fee (management fee)".
However, in reality, various other costs arise.
Typical examples are key money (security deposit) and brokerage fees.
These are particularly important because the amounts are large.
There are also various other hard-to-understand costs such as guarantee company fees, key exchange fees, fixed cleaning fees, insurance premiums, etc.
I was calculating these manually, but doing this for each room was quite troublesome.
The purpose of this tool is to determine the "actual rent" considering these costs and aim for better room hunting.
Motivating Example
For instance, let's consider an easy-to-calculate pattern where the so-called "rent" is 50,000 yen and the common service fee is 2,000 yen.
If nothing is added, of course, the monthly payment would be 52,000 yen, but if we assume key money of one month, brokerage fee of one month, fire insurance premium of 10,000 yen at the time of contract, bicycle parking fee of 3,000 yen at the time of contract, key exchange fee of 5,000 yen at the time of contract, and living there for two years, the monthly payment becomes 56,916 yen/month.
This is 4,916 yen/month more than the original amount.
On the other hand, another room with a displayed rent + common service fee of 55,000 yen had zero key money and brokerage fee.
If other conditions are the same, this would result in a payment of 55,750 yen/month, which is cheaper than the previous room.
In this way, it's common for rooms that seem cheaper than others to switch places due to other costs.
If you're looking at many rooms, it's troublesome to calculate each time.
How to Use
I believe it's... understandable by looking at the site.
Some features are still undeveloped. If you're interested, please submit an issue/pull request.
- Feature to restore input values
- Graph display of actual rent when varying the period of residence
Also, clicking on the i
for each item displays explanatory comments.
These are based on my arbitrary speculation, so if you're knowledgeable, please submit an issue/pull request for these as well.
Technical Aspects
From here on, I'll talk about the internals of the tool.
It might be easier to understand if you read this along with what's published on Github.
Also, here's the documentation for the wasm_bindgen
crate, which makes interactions with the JS world very easy. Most things are written there.
As this tool doesn't need to set up a server, I decided to make it client-side complete and publish it as a static site.
I thought about quickly writing it in React, but I decided to try Rust's WebAssembly output (wasm_bindgen), which I had been curious about but hadn't touched before.
This web application is a classic one with forms as the main feature.
It's clearly not suitable for wasm as it doesn't require performance and the logic isn't particularly complex, but I managed to implement it.
However, interactions with the JS world are quite difficult.
You can feel like you're fiddling with old-fashioned jQuery.
DOM Manipulation
First, as a difficult point, manipulating the DOM is challenging.
The web_sys
crate allows you to pull elements from HTML ids and generate new elements, so you don't have to worry about the low-level parts, but it's primitive operations, so it's painful to actually write.
For example, if you want to append <div class="hoge">fuga</div>
to a parent
element, the code would look like this:
let elem = document.create_element("div")?;
elem.set_attribute("class", "hoge")?;
elem.set_inner_html("fuga");
parent.append_child(&elem)?;
It's the same as generating elements from scratch in JS, right?
In React and others, you can use JSX, which is much easier, but unfortunately, there's no such framework for Rust at this point.
I'd like to believe that a crate that allows easy use of DSLs like JSX will appear someday due to the macro feature, but it's a mystery whether there's even a demand for it... Let's grit our teeth and increase use cases.
If I were to create my own DSL or FW, my summer vacation would be gone and it would become the next summer vacation, so I made do with "functions that make it a little easier" like the one below.
In any case, it's hard to see what kind of DOM is generated from the code.
I wonder if it could be managed with good design.
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
Along with the DOM problem, ID management is difficult.
Those who have done various things with jQuery in the past can imagine this.
You can mitigate it with good design, but considering the ease of React and Angular, the gap is huge.
I'm looking forward to future developments in this area as well.
Event Handlers
Event handlers that can be written as easily as breathing in JS require strict writing in Rust due to ownership and lifetime considerations.
Specifically, it's implemented by duplicating necessary objects with clone(), creating a Closure and registering it to the event listener, and then using forget()
on the created Closure to cause a resource leak.
In the official sample, it looks like 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();
}
It's interesting how points that we didn't care about in JS become clear.
By the way, I wondered if forget()
is unsafe, but this article explains the reason clearly.
For more details on event handlers, please also see the Closure page of wasm_bindgen
.
IDE Support
web_sys
has the following problem, and completion doesn't work in intellij-rust and rls.
This is quite painful when developing, and you actually need to compile and see the errors.
As IntelliJ is my main IDE, I haven't tried it, but there's a report in the issue below that it worked when using the Nightly version of rust-analyzer in VSCode if you want to manage, so please check it out.
- WebAssembly · Issue #3066 · intellij-rust/intellij-rust · GitHub
- Intellisense/autocompletion not working for extenral crates with optional features · Issue #637 · rust-lang/vscode-rust · GitHub
Testing
If it's self-contained within Rust without interacting with the JS world, you can write tests very comfortably as usual.
As wasm_bindgen
handles things well, the key seems to be how well you can layer and make things self-contained in the Rust world.
Other Miscellaneous Talk
- I picked up English words from the US Consumer Agency page. It was a good site with well-organized information, a pop design that's friendly, and proper warnings about fraud. The power of official sites.
- As I hadn't written a substantial amount of code recently, I feel like I couldn't split the modules well.
- As it didn't seem necessary to use material-ui, I tried using bootstrap for the first time in a while.
- Make sure to run clippy before release.
- If it's of a certain scale, it should naturally be incorporated into CircleCI etc.
- There are surprisingly many things to do before releasing an open-source web app. Many of them don't have to be done, but...
- Making the repository public on Github
- Netlify deploy settings (Github integration)
- Custom domain settings
- Favicon settings
- OGP settings
- Google Analytics settings
Summary
wasm_bindgen
and web_sys
handle primitive processing.
Types are also prepared in detail, so you can create web apps more easily than writing JS as is.
Therefore, I felt it was possible to write taking advantage of Rust's benefits, but for such a classic web app, it would be overwhelmingly more efficient to write in React.
Considering the use of WebAssembly itself, its benefits will likely become more apparent in cases where performance is needed, especially when using WebGL or WebRTC, or when most of the work is drawing to canvas.
If I have the opportunity, I'd like to create such an app as well.
Also, please make rent more understandable.
Addendum
As I received information later and researched, I found that the Yew web framework provides a JSX-like DSL (Thanks to @<em>n_s_7</em>),
and there's Dodrio written about in the Mozilla blog
Various things have already been created. As expected of Rust.
It seems none of them are intended for production use, but the future looks exciting.
I'd like to try rewriting using these or creating something new.