头图

Hi everyone, this is Zhang Jintao.

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

I recently saw what many friends said:

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 only make choices, I want them all!

Of course, the question about Rust and Go is not new, such as the previous tweet:

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

Of course, in this article I will basically not 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 foreign function interface (for simplicity, FFI will be used in the following to refer to). The first came from the Common Lisp specification, which was written on the wiki, and I did not verify it.
However, most of the languages I have used have FFI concepts/terms, such as: Python, Ruby, Haskell, Go, Rust, LuaJIT, etc.

The function of FFI is simply to allow one language to call another language, and sometimes we also use Binding to express similar capabilities.

There will be different implementations in different languages, such as cgo in Go, ctypes in Python, CAPI in Haskell (there is 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 article, for Go and Rust, their FFI needs to communicate with C language objects, and this part is actually done by the operating system according to the calling convention in the API.

Let's get to the topic.

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 more.

Create a project with Cargo

We first prepare a directory for the code of this example. (The directory I created is called go-rust )

Then use Rust's Cargo tool to create a rustdemo . Here, since I added the --lib , 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 function name exposed by Rust is called rustdemo , which receives an external parameter and prints it out. Then set a string from the Rust side.

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

Compile Rust code

We need to modify the Cargo.toml file for compilation. Note that here we have added crate-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

Check the generated file, this is a .so file (this is because I am in a Linux environment, if you are in a different system environment, it will be different)

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

Prepare Go code

The installation of the Go environment will not be repeated here, just continue to operate in our go-rust 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, import "C" is a special syntax, here is the normal C code, which needs to declare the used header files and the like.

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

At the same time, in order to allow the Go program to call the Rust function normally, here we also need to declare its header file, and write the following content lib/rustdemo.h

char* rustdemo(char *name);

Compile code

When compiling Go, we need to enable CGO (it is enabled by default), and we need to link to the rustdemo.so file built by Rust, so we put this file and its header file in the lib directory.

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

So 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

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

Summarize

This article introduces how to use Go and Rust to combine, introduces its pre-existing knowledge about FFI, and then demonstrates its complete process through a small practice.
Interested friends can practice on their own.


Welcome to subscribe to my article public account【MoeLove】


张晋涛
1.7k 声望19.7k 粉丝