Go and Rust, I want both!

Hello, I'm Zhang Jintao.

Recently, there have been some changes in the Rust community / team, so Rust has been brought to the eyes of most people again.

I've seen a lot of friends say recently:

Is Rust still worth learning? Is the community unstable

Which is better, Rust or Go?

Is Rust still worth learning?

If someone asks me these questions, my answer is:

Children make choices, I want them!

Of course, the questions about Rust and Go are not new. For example, a previous tweet:

In this article, I will introduce how to call Rust with Go.

Of course, in this article, I basically won't compare the functions of Go and Rust, or the performance of this way. Just for Fun

FFI and Binding

FFI (Foreign Function Interface) is translated as external function interface (for simplicity, FFI will be used in the following). The first specification from Common Lisp was written on the wiki, and I didn't research it.
However, the concepts / terms of FFI exist in most languages I have used, such as Python, Ruby, Haskell, Go, Rust, LuaJIT, etc.

The function of FFI is simply to allow one language to call another language. Sometimes we use Binding to represent similar capabilities.

There will be different implementations in different languages, such as CGO in Go, ctypes in Python, CAPI in Haskell (there was a ccall before), etc.
I personally feel that using FFI in Haskell is much simpler & more convenient than other languages, but this is not the focus of this article.

In this paper, for Go and Rust, their FFI needs to communicate with C language objects, which is actually completed by the operating system according to the calling convention in the API.

Let's get to the point.

Prepare Rust sample program

The installation of Rust and the basic use of Cargo tools will not be introduced here. You can go to Rust's official website to learn about it.

Create project with Cargo

Let's prepare a directory for the code of this example. (the directory I created is called go trust)

Then use Rust's Cargo tool to create a project called rustdemo. Here, because I added the -- lib option, I use its built-in library template.

➜  go-rust git:(master) ✗ mkdir lib && cd lib
➜  go-rust git:(master) ✗ cargo new --lib rustdemo
     Created library `rustdemo` package
➜  go-rust git:(master) ✗ tree rustdemo 
rustdemo
├── Cargo.toml
└── src
    └── lib.rs

1 directory, 2 files

Prepare Rust code

extern crate libc;
use std::ffi::{CStr, CString};

#[no_mangle] 
pub extern "C" fn rustdemo(name: *const libc::c_char) -> *const libc::c_char {
    let cstr_name = unsafe { CStr::from_ptr(name) };
    let mut str_name = cstr_name.to_str().unwrap().to_string();
    println!("Rust get Input:  \"{}\"", str_name);
    let r_string: &str = " Rust say: Hello Go ";
    str_name.push_str(r_string);
    CString::new(str_name).unwrap().into_raw()
}

The code is relatively simple. The exposed function of Rust is called rustdemo. It receives an external parameter and prints it. Then set a string from Rust.

CString::new(str_name).unwrap().into_raw() is converted to a raw pointer for later processing by the C language.

Compile Rust code

We need to modify the Cargo.toml file for compilation. Note that here we added crite type = ["cdylib"] and libc.

[package]
name = "rustdemo"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
libc = "0.2"

Then compile

➜  rustdemo git:(master) ✗ cargo build --release
   Compiling rustdemo v0.1.0 (/home/tao/go/src/github.com/tao12345666333/go-rust/lib/rustdemo)
    Finished release [optimized] target(s) in 0.22s

View the generated file, which is a. so file (this is because I am in Linux environment, and you will be different in other system environments)

➜  rustdemo git:(master) ✗ ls target/release/librustdemo.so 
target/release/librustdemo.so

Prepare Go code

The installation of Go environment will not be repeated here. Just continue to operate in our Go trust directory.

Write main.go

package main

/*
#cgo LDFLAGS: -L./lib -lrustdemo
#include <stdlib.h>
#include "./lib/rustdemo.h"
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func main() {
    s := "Go say: Hello Rust"

    input := C.CString(s)
    defer C.free(unsafe.Pointer(input))
    o := C.rustdemo(input)
    output := C.GoString(o)
    fmt.Printf("%s\n", output)
}

Here we use cgo. The annotation content before import "C" is a special syntax. Here is the normal C code, in which the header file used needs to be declared.

The following code is very simple. It defines a string, passes it to the rustdemo function, and then prints the string processed by C.

Meanwhile, in order to enable the Go program to call the Rust function normally, we also need to declare its header file and write the following contents in lib/rustdemo.h:

char* rustdemo(char *name);

Compile code

During Go compilation, we need to open CGO (all open by default) and link to the rustdemo.so file built by Rust, so we put the file and its header file in the lib directory.

➜  go-rust git:(master) ✗ cp lib/rustdemo/target/release/librustdemo.so lib

Therefore, the complete directory structure is:

➜  go-rust git:(master) ✗ tree -L 2 .
.
├── go.mod
├── lib
│   ├── librustdemo.so
│   ├── rustdemo
│   └── rustdemo.h
└── main.go

2 directories, 5 files

compile:

➜  go-rust git:(master) ✗ go build -o go-rust  -ldflags="-r ./lib" main.go
➜  go-rust git:(master) ✗ ./go-rust 
Rust get Input:  "Go say: Hello Rust"
Go say: Hello Rust Rust say: Hello Go

You can see that the output of the first line is passed from Go to Rust, and the output of the second line is returned from Rust to Go, which is in line with our expectations.

summary

This paper introduces how to combine Go with Rust, introduces its pre knowledge about FFI, and then demonstrates its complete process through a small practice.
Interested partners can practice by themselves.

Welcome to subscribe my official account number [MoeLove].

Tags: Go Linux Back-end Rust

Posted on Fri, 26 Nov 2021 01:17:03 -0500 by gvp16