乐趣区

关于golang:Go-和-Rust-我都要

大家好,我是张晋涛。

近期 Rust 社区 / 团队有些变动,所以再一次将 Rust 拉到大多数人眼前。

我最近看到很多小伙伴说的话:

Rust 还值得学吗?社区是不是不稳固呀

Rust 和 Go 哪个好?

Rust 还值得学吗?

这些问题如果有人来问我,那我的答复是:

小孩子才做抉择,我都要!

当然,对于 Rust 和 Go 的问题也不算新,比方之前的一条推文:

我在本篇中就来介绍下如何用 Go 调用 Rust。

当然,这篇中我基本上不会去比拟 Go 和 Rust 的性能,或者这种形式的性能之类的,Just for Fun

FFI 和 Binding

FFI (Foreign Function Interface) 翻译过去叫做内部函数接口(为了比较简单,下文中都将应用 FFI 指代)。最早来自于 Common Lisp 的标准,这是在 wiki 上写的,我并没有去考据。
不过我所应用过的绝大多数语言中都有 FFI 的概念 / 术语存在,比方:Python、Ruby, Haskell、Go、Rust、LuaJIT 等。

FFI 的作用简略来说就是容许一种语言去调用另一种语言,有时候咱们也会用 Binding 来示意相似的能力。

在不同的语言中会有不同的实现,比方在 Go 中的 cgo , Python 中的 ctypes,Haskell 中的 CAPI(之前还有一个 ccall)等。
我个人感觉 Haskell 中用 FFI 相比其余语言要更简略 & 不便的多,不过这不是本篇的重点就不开展了。

在本文中,对于 Go 和 Rust 而言,它们的 FFI 须要与 C 语言对象进行通信,而这部分其实是由操作系统依据 API 中的调用约定来实现的。

咱们来进入正题。

筹备 Rust 示例程序

Rust 的装置和 Cargo 工具的根本应用,这里就不介绍了。大家能够去 Rust 的官网进行理解。

用 Cargo 创立我的项目

咱们先筹备一个目录用来放本次示例的代码。(我创立的目录叫做 go-rust

而后应用 Rust 的 Cargo 工具创立一个名叫 rustdemo 的我的项目,这里因为我减少了 --lib 的选项,应用其内置的 library 模板。

➜  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

筹备 Rust 代码

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()
}

代码比较简单,Rust 裸露进去的函数名叫做 rustdemo,接管一个内部的参数,并将其打印进去。之后从 Rust 这边再设置一个字符串。

CString::new(str_name).unwrap().into_raw() 被转换为原始指针,以便之后由 C 语言解决。

编译 Rust 代码

咱们须要批改下 Cargo.toml 文件以便进行编译。留神,这里咱们减少了 crate-type = ["cdylib"]libc

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

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

[dependencies]
libc = "0.2"

而后进行编译

➜  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

查看生成的文件,这是一个 .so 文件(这是因为我在 Linux 环境下,你如果在其余零碎环境下会不同 )

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

筹备 Go 代码

Go 环境的装置之类的这里也不再赘述了,持续在咱们的 go-rust 目录操作即可。

编写 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)
}

在这里咱们应用了 cgo,在 import "C" 之前的正文内容是一种非凡的语法,这里是失常的 C 代码,其中须要申明应用到的头文件之类的。

上面的代码很简略,定义了一个字符串,传递给 rustdemo 函数,而后打印 C 解决后的字符串。

同时,为了可能让 Go 程序能失常调用 Rust 函数,这里咱们还须要申明其头文件,在 lib/rustdemo.h 中写入如下内容:

char* rustdemo(char *name);

编译代码

在 Go 编译的时候,咱们须要开启 CGO(默认都是开启的),同时须要链接到 Rust 构建进去的 rustdemo.so 文件,所以咱们将该文件和它的头文件放到 lib 目录下。

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

所以残缺的目录构造就是:

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

2 directories, 5 files

编译:

➜  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

能够看到,第一行的输入是由 Go 传入了 Rust,第二行中则是从 Rust 再传回 Go 的了。合乎咱们的预期。

总结

本篇介绍了如何应用 Go 与 Rust 进行联合,介绍了其前置对于 FFI 相干的常识,后续通过一个小的实际演示了其残缺过程。
感兴趣的小伙伴能够自行实际下。


欢送订阅我的文章公众号【MoeLove】

退出移动版