Rust for web development? The case for webassembly with Rust.
A little while ago I was talking with a friend about web development. I was developing something in React and he mentioned using webassembly. I had to admit, I had totally forgotten about it. But it sparked my curiosity. When C# was first introduced on the market I thought it would be a passing fancy, so I didn’t invest a lot of time in it and missed the boat on that one. I wasn’t going to let that happen with webassembly. So I started doing some research. But would I be using C#, or would I use something else to try webassembly? I went to youtube and found people enthousiastic about Rust. But is Rust a good choice for web development?
Rust is a language that specialises in speed and memory safety. It does this by using some very strict rules about what code “owns” and has access to certain memory locations and how that ownership moves from one place to the next. It also has similar rules for where data can go, how it is shared, what code can modify something, how long data is guaranteed to exist, etc, etc.
By using all these rules, Rust can boast being memory safe without resorting to a garbage collector such as C#. And because it doesn’t have (or need) such a garbage collector, it can be blindingly fast. It can even be faster than many other native languages like C++. Moreover, there are some interesting web development frameworks for Rust, such as Yew.
Anyone who has done some development with React, will likely feel like Yew is very familiar. That’s because Yew uses another cool feature of Rust: its extremely powerful macro abilities, which allows you to use your own code at compile time. Yes, you’re reading that right! You can even define a macro in Rust that tokenises and parses some input and produces legal Rust code. That means, you can natively include HTML code in Rust by using the html! macro in Yew.
Great! Let’s all switch to Rust and Yew!
Wow.. Hold your horses.. All of this may be very cool, but it doesn’t show whether rust is a good choice for web development or not. I found Rust does come with some drawbacks. It may be very fast, but does web really need all this extreme speed optimization? And at what price do you get this benefit? In the 3 weeks or so that I’ve experimented with Rust, I found it not very easy to get anything done. Especially when sharing data between multiple bits of code, if you want to make a callback function to handle an event (such as a mouse click or dragging operation) where you need to update some data on an object declared outside that callback function, or when accessing some other data that (according to the code) can’t be guaranteed to exist at that time, then you’ll be opening a big can of worms.
Now, be aware that this is my personal and professional opinion, and there may be some people who can make wonderful webpages with Rust and yew, or other Rust web development frameworks. But to me it feels like it’s not quite ready for commercial use yet. I think the required effort, energy, and time is currently not worth the added performance or memory safety. A garbage collector provides similar safety and I think the performance hit of a garbage collector is well worth the reduced effort of getting to learn the language and getting anything other than the very trivial to work in Rust.
Stuff like callbacks in a struct that want to update something in ‘self’ can take hours to figure out because of lifetimes, borrowing, moving, copying, etc. Even when using Rc<RefCell<Self>> it can still be difficult or feel downright impossible to get something to work because of errors like ‘self escapes the method body here’ or something vague like that. Let me be clear that when it comes to error reporting, Rust does an excellent job. Probably the best I’ve seen so far. But I don’t think that helps a lot when after several hours of trying to get some callback function to work that updates data outside that function, it’s either still not working, the code has become a horrible mess, or it’s working while using a lot of obscure code using Rc<RefCell<T>> (which actually resorts to using unsafe code to get the mutable sharing to work anyway). Such code makes it difficult to read and can feel impossible and illogical. For instance, this works in Rust:
let divclone = Rc::clone(&self.div);
let div = divclone.borrow_mut();
let div = div.as_ref().unwrap();
But when you try to shorten this code by bringing the last two lines together, this does not work:
let divclone = Rc::clone(&self.div);
let div = divclone.borrow_mut().as_ref().unwrap();
// This gives an error because the temporary value from divclone.borrow_mut()
// goes out of scope at the semicolon at the line end, but which is needed to
// guarantee the legal value of the div variable.
// But even when redefining the div variable in the last line of the working example,
// that temporary value apparently does not go out of scope, which (to me anyway)
// seems totally illogical
The examples, be it for rust, yew, wasm_bindgen, web_sys, or the like, are severely lacking in this aspect. They are out of date, and don’t show many very common use cases.. A simple use case in yew using a closure in a struct component where you need to both reference self AND want to update something in the context that’s declared on the app component level, can be impossible to figure out for a beginner. This is demotivating, demoralising, and tedious.
The same criticism that I have for angular (needing to spend more half your time working on code for the framework), I have for Rust. It simply takes too long and too much effort to work through the insanely complicated rules of Rust to justify using it for making a commercial web application. In the case of using it for kernel development, I absolutely understand and fully endorse the use of Rust. But why would I want to use Rust for making a web page that takes me three days, a huge headache, and spending most of my time looking at compiler errors, stackoverflow, rust examples and documentation, and very opaque code, to make a web app that increases a counter when I press a button? (Something a serious business customer would never ask for)
Sure, I know those examples are just building blocks and demonstrations of principles and that there are easier ways to get a counter to work than using a struct with a render method which manipulates the DOM and stores the counter value in the struct’s data. But this is just an example. I’m not doing this as a hobby. Eventually there will be data that I will need to store in the struct. I want to be able to make something bigger that I can sell, which means I need to be able to make my own microarchitectures and I need to be able to do more than just increasing a counter when the user clicks a button. I want to be able to drag dividers between areas, drag panels and dock them somewhere, synchronise scrolling between two scrollable areas, etc. But even in Yew, the standard dragging behaviour of a component is not overridden, which means that when you implement the buttondown, mousemove, and buttonup events to implement a draggable divider, it will only work in about 25-75% of the times you drag it. In other cases, the browser will initiate the default dragging action. This is a problem I didn’t have in react so I didn’t anticipate it. In yew I found after several days of debugging and in despair finally implementing the same thing in plain javascript and getting the same problem, that I needed to implement a dragstart event handler to prevent the default behaviour.
Yew helps a lot with its html! macro, but I don’t like the focus on function components over struct components (a personal preference; and yes, I do dislike the same thing about React). I think this is a tendency that has blown over from javascript and (in my opinion) it’s not a good development because using structs in Rust is a lot more powerful when used right. Still, Yew may offer a handy context object to provide information that applies to all components (such as style information, which is useful when you want to offer darkmode and lightmode for instance), but after several days of searching, experimenting, and even with the help of ChatGPT, I could not find a way to change some data in the context (such a menu item to change between dark mode and light mode), other than boring a callback property through several layers of components. This is exactly what the context object was created for, so it seems very odd that changing the context has to be done this way.
Now, sure, you could argue that the performance and stability gains will always stay while I only need to write the source code once.. But as we’ve seen in the real world: commercial (web) applications are always in motion. Vendors always keep changing the code and adding new functionality, rewriting parts of the product, etc..
Entropy in source code
Pragmatic Dave Thomas and Andrew Hunt once wrote about code entropy. Code in development tends to get messier over time. Comment has a tendency to drift away from the code that it relates to. Functions tend to sometimes have side effects that are forgotten over time, causing future malfunctions. Bugs get patched over and worked around. Rust is not free of this phenomenon. Just last night I saw someone on rust-lang.org reply a question about a ‘self escapes the method body here’ error where the programmer asking the question was apparently misusing a lifetime specification to work around one of Rust’s many rules.. It could be argued that Rust, with its less than trivial rules, could suffer more from code entropy than most languages. That means as a business owner, when developing a commercial web based product with Rust, you should really understand what you’re investing in.
Never forget that while it may be possible to make something beautiful in Rust today.. Or at least, this year (I would seriously challenge anyone who says they can make some commercial MVP in rust in a day).. Your future colleagues will be highly capable of messing it up. Not because of malicious intent, but simply because code never explains the full intent of the programmer who wrote it. If Rust’s complexity invites more of this kind of entropy, that would likely lead to commercial web applications in Rust quickly becoming impossible to maintain.
Conclusions: Is Rust a good choice for web development?
When you’re working on an operating system kernel, medical application, vehicle control system, astrophysics or quantum physics application, high-end computer game, number crunching, or something else where you really need that extra stability and performance, it totally makes sense to use Rust. But for webassembly, or any kind of user interface work, where your application will spend most of its time waiting on user input anyway, I don’t see the added benefit of using Rust.. Perhaps if anyone were to create a language which would be like a superset of Rust (like Typescript is to Javascript), making lots of things easier and more quickly to implement, so that it would be easier to be productive and write maintainable code which is simple to understand, debug, add to, and change, then maybe it could have an interesting future in web and I will definitely give it a try. Because whether you like Rust or not, I think there is no denying it’s pretty awesome technology, and (in my humble opinion) one of the major innovations in software development of the last 20 years. Just at the moment, Rust is probably not a great choice for web development or any kind of user interface work.