DEV Community

Cover image for Calling Rust code from Go - the Gambiarra way
Gabriel Grubba
Gabriel Grubba

Posted on • Updated on

Calling Rust code from Go - the Gambiarra way

Ferrys gopher

Did you know that Rust has been voted the 'most loved programming language' for five years in a row by the Stack Overflow community? It's no wonder why Rust has gained such popularity - its unique features make it a powerful tool for systems programming. But what if you want to use Rust in conjunction with another language? In this post, we'll explore how you can call Rust code from Go with a more "gambiarra" approach, and how this approach can be useful for migrating apps to Rust.

A more correct way.

Before we start we need to address the post that triggered this idea in me, is this a great post(RUSTGO: CALLING RUST FROM GO WITH NEAR-ZERO OVERHEAD) from Filippo Valsorda, I wanted to make a version with this but in a different(probably a way less performant version of it) but with the migration of projects in mind instead of having something living together, and calling with thought an FFI. The idea is simple and there is a repo with a minimal viable example here. Without further ado, to make this happen the only thing you will need to add to your rust code is clap if it does not have it, and yes, you probably see where we are going with this. We are making our Rust code a CLI program and using the thing every language that I know has, the ability to call other programs, something as we do in Go with exec.Command from the os/exec library.

How does this work?

Visual model of what is happing

Visual model of what is happening.

The steps you will need to follow are:

  1. Create entry points for your code with the parameters you need (clap will help a lot with this). Your rust code should return as output like text or it can write to a file that should be read from your main app program later. The .Output method will return a buffer with every single thing that was printed from your Rust program, if you expect from you rust code a more complex response, write to a file and read.
  2. Compile your rust to an executable in the host machine, which can be done before starting the main app.
  3. In your main app, for example in Go, when you need to call this module/feature/thing-that-you-need-that-is-in-rust you make a terminal call to it using your main app and unmarshal it as it was another API call.

And there you go, you have yourself two living projects that can talk to each other. This can be really useful when migrating apps to Rust.

In practice

For to have at least an example of this in action I will show what I did in the example project:

Our Rust code is below:

use clap::Parser;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[arg(short, long)]
    name: String,

}

fn main() {
    let args = Args::parse();

    println!("Hello, {}!", args.name)

}
Enter fullscreen mode Exit fullscreen mode

Is the example from clap as its starting point and well what we will do is compile this code using cargo build --release and then in our Go code we will call this binary like this:

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    output, err := exec.Command("./rust-code/target/release/rust-code", "--name", "John").Output()
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(string(output)) // will print "Hello, John!"
}
Enter fullscreen mode Exit fullscreen mode

Now we have something that works, this can be done in almost any language that compiles to an executable and it can be called within the command line. Sure that is not the most reliable way of doing things, as we do not have a great type system in our favor and we hurt performance as we are creating and making objects all the time but is the most straightforward way and in my opinion the easiest way to go from language x to language y.

Top comments (3)

Collapse
 
avelino profile image
Thiago Avelino

Your example using exec you are calling binary, it would not matter if it was written in rust or any other language

one way to call "functions" of a language inside Go is using the plugin, where you load the operating system library (files .so) and don't execute the binary as in your example above

I am using this plugin approach in prestd, here is a thread discussing how it works

Collapse
 
grubba profile image
Gabriel Grubba • Edited

Your example using exec you are calling binary, it would not matter if it was written in rust or any other language

Yes! this was mostly the example that steped upon, you are 100% correct, any compiled language should do the trick.

Never seen this before go-plugin, will check it out! Also loved the prest project
thanks a lot for the comment!

Collapse
 
avelino profile image
Thiago Avelino

Come contribute with us \o/, we need more people helping the project evolve