“This is the fifth day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

Hello everyone, I’m Zhang Jintao.

There have been some recent changes in the Rust community/team, so Rust has once again been brought to most people’s attention.

I’ve seen a lot of friends say things recently:

Is Rust still worth learning? The neighborhood is not stable

Which is better, Rust or Go?

Is Rust still worth learning?

If I were asked these questions, my answer would be:

Kids make choices. I want them all!

Of course, questions about Rust and Go aren’t new, like this earlier tweet:

In this article, I’ll show you how to call Rust with Go.

Of course, I’m not going to compare the functionality of Go and Rust, or the performance of this approach, for the most part in this article, Just for Fun

FFI and Binding

FFI (Foreign Function Interface) is translated as the external Function Interface (FFI will be referred to in the rest of this article for simplicity). The original specification came from Common Lisp, which was written on a wiki and I didn’t check it out. But the concept/term FFI exists in most of the languages I’ve used: Python, Ruby, Haskell, Go, Rust, LuaJIT, etc.

FFI simply allows one language to call another, and we sometimes use Binding to express similar capabilities.

There are different implementations in different languages, such as CGO in Go, ctypes in Python, CAPI in Haskell (and a ccall before that), etc. I personally find FFI easier and more convenient in Haskell than in other languages, but that’s not the point of this article.

For Go and Rust in this article, their FFI needs to communicate with C objects, which is actually done by the operating system according to the calling convention in the API.

Let’s get down to business.

Prepare the Rust sample program

The installation of Rust and basic use of the Cargo tool are not covered here. You can check out Rust’s website.

Create projects with Cargo

Let’s start with a directory where we can put the code for this example. (The directory I created is called go-rust)

Then use Rust’s Cargo tool to create a project called RustDemo, which uses its built-in Library template because I added the –lib option.

➜ go-rust git:(master) qualify mkdir lib &&cdLib ➜ go rust git:(master) onto university cargo new --lib rustdemo Created library 'rustdemo' package ➜ go rust git:(master) onto university tree Rustdemo rustdemo ├ ─ ─ Cargo. Toml └ ─ ─ the SRC └ ─ ─ lib. Rs 1 directory, 2 filesCopy the code

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()
}
Copy the code

The code is relatively simple. Rust exposes a function called Rustdemo that takes an external argument and prints it out. Then set another string from Rust.

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

Compiling Rust code

We need to modify the Cargo. Toml file to compile. Note that we have added crate-type = [“cdylib”] and libc.

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

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

[dependencies]
libc = "0.2"
Copy the code

And then compile it

➜ rustdemo git:(master) qualify cargo build --release Compiling rustdemo v0.1.0 (/home/tao/go/src/github.com/tao12345666333/go-rust/lib/rustdemo) Finished release [optimized] target(s)in0.22 sCopy the code

Look at the generated file, this is a.so file (this is because I am running Linux, you will be different if you are running other systems)

➜ rustdemo git: (master) ✗ ls target/release/librustdemo. So the target/release/librustdemo. SoCopy the code

Prepare the Go code

Installation of the Go environment will not be described here, just continue to operate in our Go-rust directory.

Write the main. Go

package main

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

import (
	"fmt"
	"unsafe"
)

func main(a) {
	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)
}
Copy the code

Here we are using Cgo. The comment before import “C” is a special syntax. Here is normal C code, which declares the used header file and so on.

The following code is simple: define a string, pass it to the rustdemo function, and print the C processed string.

In order for the Go program to call the Rust function, we also need to declare its header file. In lib/rustdemo.h, write the following:

char* rustdemo(char *name);
Copy the code

Compile the code

When Go is compiled, we need to enable CGO (which is always enabled by default) and link to rustDemo. so, a file built by Rust, so we put that file and its header in the lib directory.

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

So the complete directory structure is:

➜ go-rust git:(master) go onto those who qualify. ├── go. ├── ─ go main.go 2 directories, 5 filesCopy the code

Compile:

➜ go-rust git:(master) qualify go build-o go-rust-ldflags ="-r ./lib"Main. go ➜ go-rust git:(Master) Qualify./go-rust rust get Input:"Go say: Hello Rust"
Go say: Hello Rust Rust say: Hello Go
Copy the code

As you can see, the output in the first line is passed from Go to Rust, and in the second line is passed back from Rust to Go. In line with our expectations.

conclusion

This article describes how to use Go in combination with Rust, introducing the knowledge related to FFI in advance, and then demonstrating the complete process through a small practice. Interested partners can practice.


Please feel free to subscribe to my official account [MoeLove]