Rust - amark/gun GitHub Wiki

A JavaScript Hacker's Cheatsheet Guide to Rust

While Mark is porting his database to Rust, all he wanted to know was how to do this javascript in Rust:

var Groceries = {
  add_milk: function(obj, value){
    obj.milk = value;
  }
}

var obj = {};
Groceries.add_milk(obj, "1gallon whole");
console.log(obj.milk);

if(undefined !== obj.milk){
  console.log("here!", obj.milk);
}

Object.keys(obj).forEach(function(key){
  console.log(key, obj[key]);
});

Here's how: (try it on the online Rust playground)

use std::collections::HashMap;

type Object = HashMap<String, Value>;
#[derive(Debug)]
enum Value {
    Null(Option<bool>),
    Bit(bool),
    Number(f32),
    Text(String),
    Link(Object)
}

struct Groceries;
impl Groceries {
    fn add_milk(obj: &mut Object, value: Value) {
        obj.insert("milk".to_string(), value);
    }
}

fn main() {
    let mut obj = Object::new();
    Groceries::add_milk(&mut obj, Value::Text("1gallon whole".to_string()));
    println!("{:?}", obj.get("milk"));

    if let Some(value) = obj.get("milk") {
        println!("here! {:?}", value);
    }
    
    for key in obj.keys() {
        println!("{}: {:?}", key, obj.get(key));
    }
}

This page will be updated as his learnings progress.

If you see anything that could be simplified or improve performance, please contact him immediately.

Why

In 2019, I saw Rik Arends build a code editor from scratch, including font rendering and text selection, that could scroll through tens of thousands of lines of colorized text in milliseconds, all in 300KB. And most mind blowing of all, it worked on every platform: all web, all desktops, all mobile.

To compare, the smallest build of Sublime Text I'd seen was 3MB, with stuff like VS Code at 100MB+. The vim WASM is 730KB, though I'm not sure how efficiently that was built.

At that moment, I knew I was dealing with futuristic alien technology stuff, you know, like 1990s-level 3D video games and OS that could fit on a floppy disk. Forget webpack cult, this was the new religion I was going to start. Tiny, performant, universal. After all, GUN core does 5K ops/sec and is only 9KB of JavaScript (+3KB default adapters), compare to Pouch (37KB), jQuery (30KB), Vue (23KB), etc.

Whatever language Rik used, I'd have to admit defeat it was objectively superior. If I could dream, it would be Haskell or APL. But what if it was Java? The amount of shame I'd feel...

Oh phew, Rust!

Frustrations

Outstanding compliments to Rust and the Rust community, docs, and everything. However, the docs can be deceptive as they try to ease you into the language (which is probably a good thing), but can be triggering. An example may be beautifully simple, almost look python-like, and will even run! But if you try to edit anything slightly or make the example a reusable pattern, the syntax suddenly changes by 2X before it can compile.

But with so many guides out there, why yet another one?

Why this guide?

This "guide" is meant for pragmatists who don't care about language commitment, just performance minded JSers who want to port pieces of their code to Rust and ship faster. It is also case-specific targeted to me - the JavaScript patterns I use:

  • No dependencies
  • No build systems
  • No "new" JS language features
  • No classes, just utility functions
  • "Just give me objects (hashmaps), function calls, if statements and loops!" Turing Completeness

It will be updated as I make progress porting GUN to Rust - which is extremely low priority task right now. It will contain my generic learnings and "patterns" as I go along my way.

If you're interested in seeing this sooner, join the community and schedule a "let's port GUN to Rust!" pair programming call that we livestream!

Objects in Objects

In JS, this is totally OK:

var child = {hello: "world"};
var mom = {name: "Alice", kid: child};
var dad = {name: "Bob", son: child};

But rust does not like that child is in 2 places:

fn main() {
    let mut child = Object::new();
    child.insert("hello".to_string(), Value::Text("world".to_string()));
    
    let mut mom = Object::new();
    mom.insert("name".to_string(), Value::Text("Alice".to_string()));
    mom.insert("kid".to_string(), Value::Link(child));
    
    let mut dad = Object::new();
    dad.insert("name".to_string(), Value::Text("Bob".to_string()));
    dad.insert("son".to_string(), Value::Link(child)); // RUST ERROR!!!
}

One way to fix this is by increasing the number of reference counts child has. This is probably the correct approach and what you should do. However, we're then not allowed to delete the child object unless we remove it from all the parents first. This is wrong for what I want to do in building a graph database, because even if I evict child from memory, I still want mom and dad to have properties that exist.

So what I'm going to do instead is have an all object that stores all my objects, each with a unique id. Then I'll have mom and dad store the id of child, where I can then grab it from the all object.

To put this to practice, let's try filtering an object

var obj = {a: 1, b: "hello world", c: {x: "yo"}}, filter = {};
Object.keys(obj).forEach(function(key){
  var value = obj[key];
  if("string" == typeof value){
    console.log("here!", value);
  } else
  if(value instanceof Object){ // careful, JS thinks a lot of things are Objects.
    console.log("link!", value.x);
    filter[key] = value;
  }
});
console.log(filter);

now let's make sure we can do something like that in Rust:

fn main() { // To get this code to run in the playground, only copy & replace the main function!
    let mut sub = Object::new();
    sub.insert("x".to_string(), Value::Text("yo".to_string()));
    
    let mut obj = Object::new();
    obj.insert("a".to_string(), Value::Number(1.0));
    obj.insert("b".to_string(), Value::Text("hello world".to_string()));
    obj.insert("c".to_string(), Value::Link(sub));
    
    let mut filter = Object::new();
    for key in obj.keys() {
        match obj.get(key) {
            Some(Value::Text(value)) => {
                println!("here! {:?}", value);
                filter.insert(key.to_string(), Value::Text(value.to_string()));
            },
            Some(Value::Link(value)) => {
                println!("link! {:?}", value.get("x"));
            },
            Some(_) => {
            },
            None => {
                
            }
        }
    }
    println!("filtered: {:?}", filter);
}
⚠️ **GitHub.com Fallback** ⚠️