Go调用Rust方法及外部函数接口前置


Posted in Golang onJune 14, 2022

前言

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

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

  • Rust 还值得学吗?社区是不是不稳定呀
  • Rust 和 Go 哪个好?
  • Rust 还值得学吗?

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

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

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

Go调用Rust方法及外部函数接口前置

我在本篇中就来介绍下如何用 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 相关的知识,后续通过一个小的实践演示了其完整过程。 感兴趣的小伙伴可以自行实践下。

以上就是Go调用Rust方法及外部函数接口前置的详细内容,更多关于Go调用Rust外部函数接口前置的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
golang import自定义包方式
Apr 29 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
浅谈golang 中time.After释放的问题
May 05 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Go语言实现Snowflake雪花算法
Jun 08 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
golang使用map实现去除重复数组
Apr 14 Golang
golang的文件创建及读写操作
Apr 14 Golang
GO语言异常处理分析 err接口及defer延迟
Apr 14 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 #Golang
详解Go语言中Get/Post请求测试
Golang实现可重入锁的示例代码
May 25 #Golang
Go web入门Go pongo2模板引擎
May 20 #Golang
Go语言入门exec的基本使用
May 20 #Golang
Golang并发工具Singleflight
May 06 #Golang
深入理解 Golang 的字符串
May 04 #Golang
You might like
PHP采用XML-RPC构造Web Service实例教程
2014/07/16 PHP
php的debug相关函数用法示例
2016/07/11 PHP
PHP入门教程之上传文件实例详解
2016/09/11 PHP
javascript开发技术大全 第4章 直接量与字符集
2011/07/03 Javascript
JS基础之undefined与null的区别分析
2011/08/08 Javascript
sencha touch 模仿tabpanel导航栏TabBar的实例代码
2013/10/24 Javascript
javascript利用apply和arguments复用方法
2013/11/25 Javascript
JavaScript获取表格(table)当前行的值、删除行、增加行
2015/07/03 Javascript
关于backbone url请求中参数带有中文存入数据库是乱码的快速解决办法
2016/06/13 Javascript
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
2016/12/15 Javascript
jQuery中Chosen三级联动功能实例代码
2017/03/07 Javascript
js实现会跳动的日历效果(完整实例)
2017/10/18 Javascript
jQuery第一次运行页面默认触发点击事件的实例
2018/01/10 jQuery
jquery+css3实现熊猫tv导航代码分享
2018/02/12 jQuery
JS获取并处理php数组的方法实例分析
2018/09/04 Javascript
利用jquery和BootStrap实现动态滚动条效果
2018/12/03 jQuery
一步一步实现Vue的响应式(对象观测)
2019/09/02 Javascript
html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】
2019/09/10 jQuery
[01:12:40]DOTA2-DPC中国联赛 正赛 DLG vs XG BO3 第三场 1月25日
2021/03/11 DOTA
Python的Flask框架中集成CKeditor富文本编辑器的教程
2016/06/13 Python
django 控制页面跳转的例子
2019/08/06 Python
python实现ip地址查询经纬度定位详解
2019/08/30 Python
Python编写memcached启动脚本代码实例
2020/08/14 Python
html5 拖拽及用 js 实现拖拽功能的示例代码
2020/10/23 HTML / CSS
strstr()的简单实现
2013/09/26 面试题
如何判断一段程序是由C 编译程序还是由C++编译程序编译的
2013/08/04 面试题
什么是静态路由,其特点是什么?什么是动态路由,其特点是什么?
2013/07/26 面试题
linux面试题参考答案(7)
2012/10/29 面试题
中学生差生评语
2014/01/30 职场文书
2014领导班子正风肃纪思想汇报
2014/09/18 职场文书
领导干部作风建设总结
2014/10/23 职场文书
学校政风行风整改方案
2014/10/25 职场文书
在HTML5 localStorage中存储对象的示例代码
2021/04/21 Javascript
Go 自定义package包设置与导入操作
2021/05/06 Golang
PostgreSQL解析URL的方法
2021/08/02 PostgreSQL
解决Windows Server2012 R2 无法安装 .NET Framework 3.5
2022/04/29 Servers