Hello,
This post announces my fork of rustpad that adds remote execution capabilities for a lot of languages (using piston), including some backstory and tradeoffs/remarks.
Why remote execution is nice?
I have been doing light coding interviews (at this point I would call these in-person technical screenings really, imagine easy leetcode problems) for 3 years now, and remote-first work means I had to find a solution to quickly get feedback from candidates and share a workspace.
Having a quick scratchpad like that also allows to quickly share code and talk
about it, just like what the I Run Code
Discord bot does, but with yet more
interactivity between participants.
Deciding to craft a solution
The first insecure solution that came to mind to me was to just use tmate to create quickly a shareable tmux session where I could create a small workspace and candidates could “simply” use Vim or Emacs to work on the problems. There are two big enough problems with the approach:
- Familiarity
- candidates don’t have to know tmux well or vim or Emacs well to be good developers, but tmate adds a lot of friction that adds some bias towards those tools
- Security
- giving a total stranger a tmux session in my work device does not sound like a good idea in general.
All other “easy win” solutions I could think of were basically hitting that Familiarity bias issue and/or were missing the ability to run code samples to check mini tests. These tools also have a third big problem
- Extra setup
- the candidate is asked to run plugin installation or even signups to third-party platforms to enable a shared workspace, which is really overkill when I just ask “can you write a function that checks whether 2 words are anagrams?”
The list included:
- Live Share that only runs in VSCode and needs the plugin to be installed (and a github sign-up is mandatory)
- Code with me that only runs in JetBrains editors and needs the plugin to be installed
- Floobits that needs a plugin to be installed, and just can’t work with VSCode or JetBrains products, and needs an account on Floobits, and lacks sharing execution
This is why I caved and looked for the SaaS alternatives like coderpad and
codeinterview. I used to use the free no-login trials of services like
codeinterview.io and coderpad to run the show test the tools (when I just stay
30 minutes maximum with a candidate I never hit the trial limits). It seemed
really good on all fronts from my extensive testing:
- Familiarity
- the web editor is a “modern” vanilla experience so most candidates got accustomed either immediately or very quickly.
- Security
- all our personal files are well away of the web interface. Everyone keeps their privacy as no one needs to screen share or something.
- Extra setup
- No extra setup as it’s all in the browser.
Things changed though, those companies have considered that I’m taking too long to decide on a platform, and/or decided that they don’t need to be that generous, they shut down those free, no strings attached pads. I’ve been wanting an alternative that doesn’t require signing up ever since, even if that meant self-hosting to pay for the infra myself.
I’ve been eyeing cool collaboration tools like rustpad (i.e. shared pad with syntax highlighting), but it lacked the ability to have shared compilation/execution of the code. A friend of mine made a patch to send the text to rust-lang playground (we’re a rust shop at my current position) when we tested candidates, but I didn’t feel good riding Rust infra or having to deal with a lot of APIs for different languages (e.g. python, javascript…). So I decided to go for integration of Piston backend after seeing it work so well on Discord and that self-hosting a backend seemed pretty easy.
The stack
The product is a React application communicating through websockets to a Rust central server using wasm bindings. The architecture is also summarized in the README of the repository, but basically the front-end application is a websocket client that communicates with a central server to apply Operational Transform to the shared content, and there are extra message types to deal with the metadata surrounding
- users,
- syntax highlighting, and
- communicating with a Piston backend.
The wasm bindings allow to implement only once the Operational Transform logic (and send it, serialized to JSON, to the server), and the client just updates on received messages.
This project is also nice for me because I’m very likely to do more React at my next job, and I was looking for a hobby front-end project to go further than the sole jquery crash course I made a while ago.
I am familiar to this client-server protocol, as I also wrote a javascript SDK based on wasm bindings to business classes (that do the logic in Rust) wrapped in a RPC over websocket protocol; imagine gRPC, but instead of using Protobuf on the wire we use a more specialized format to have better compression performance in our restricted payload types (and the transport is websocket).
Current state, tradeoffs, and future
Since the central Rust server is a single client to the Piston API, I preferred deploying together a Rustpad instance and the Piston one so that I would not hit the rate limits of the public Piston instance. That instance is hosted on a personal server (the same one as this blog at least for the time being), exposed on https://pad.gagbo.net.
The main relevant tradeoff that this application has, compared to SaaS solutions, is that the “runtimes” that Piston provides to execute code don’t have “popular” libraries baked in the environment. For example you cannot use rand or serde crate with the Rust runtime.
As far as backend features go, I think that I’m done with it, it definitely scratch that itch for me. I’ll add the ability to choose the runtime when there are multiple ones for a single language (currently you don’t have a choice and just get the default one; usually the more recent one) I’m more concerned with getting to know front-end better and the various Rust <-> WASM <-> Javascript interactions that exist to see how performance is affected by this new (to me) memory model. Examples include:
- Sharing more types from rustpad-server to rustpad-wasm so they are available in typescript directly instead of having a more brittle serialization/deserialization (basically, applying DRY on the websocket payload declarations)
- Trying to refactor the state with Redux to see if this application is a good use case for it.
- Add more themes to the application, and make it prettier as a whole.
That also means that I’m going to be picky and critical of possible patches that may come. For the record I send a notice upstream about this derivative work to see if they were interested in reviewing/merging the patch and I interpret the response as
This project is done and won’t add new features, feel free to fork it
Therefore I forked it and decided to just follow my personal feature-set for my fork. Everything is MIT so you’re free to do the same thing if there’s a feature you’d like that I don’t want to add or maintain.
Have fun, Gerry