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 相关文章推荐
win10下go mod配置方式
Apr 25 Golang
golang http使用踩过的坑与填坑指南
Apr 27 Golang
解决golang在import自己的包报错的问题
Apr 29 Golang
golang 实现Location跳转方式
May 02 Golang
golang gopm get -g -v 无法获取第三方库的解决方案
May 05 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
Golang的继承模拟实例
Jun 30 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
Go语言空白表示符_的实例用法
Jul 04 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
Go语言编译原理之源码调试
Aug 05 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调用MsSQL存储过程使用内置RETVAL获取过程中的return值
2013/07/03 PHP
非常好用的Zend Framework分页类
2014/06/25 PHP
PHP编译安装时常见错误解决办法
2015/05/28 PHP
Yii框架ACF(accessController)简单权限控制操作示例
2019/04/26 PHP
PHP pthreads v3下worker和pool的使用方法示例
2020/02/21 PHP
javascript学习(一)构建自己的JS库
2013/01/02 Javascript
jquery遍历checkbox的注意事项说明
2014/02/21 Javascript
jQuery实现图片上传和裁剪插件Croppie
2015/11/29 Javascript
javascript图片延迟加载实现方法及思路
2015/12/31 Javascript
Bootstrap每天必学之工具提示(Tooltip)插件
2016/04/26 Javascript
详解js的延迟对象、跨域、模板引擎、弹出层、AJAX【附实例下载】
2016/12/19 Javascript
vue的一个分页组件的示例代码
2017/12/25 Javascript
vue结合element-ui使用示例
2019/01/24 Javascript
详解JavaScript中的函数、对象
2019/04/01 Javascript
JS中的算法与数据结构之字典(Dictionary)实例详解
2019/08/20 Javascript
jQuery实现高度灵活的表单验证功能示例【无UI】
2020/04/30 jQuery
用JavaScript实现贪吃蛇游戏
2020/10/23 Javascript
[00:10]DOTA2全国高校联赛速递
2018/05/30 DOTA
Python多进程通信Queue、Pipe、Value、Array实例
2014/11/21 Python
python中常用检测字符串相关函数汇总
2015/04/15 Python
python爬虫headers设置后无效的解决方法
2017/10/21 Python
解读python logging模块的使用方法
2018/04/17 Python
如何使用Python实现斐波那契数列
2019/07/02 Python
pandas如何处理缺失值
2019/07/31 Python
Python numpy.zero() 初始化矩阵实例
2019/11/27 Python
三星美国官网:Samsung美国
2017/02/06 全球购物
副董事长岗位职责
2014/04/02 职场文书
银行委托书范本
2014/04/04 职场文书
幼师求职自荐信
2014/05/31 职场文书
销售代理协议书
2014/09/30 职场文书
2014年教务处工作总结
2014/12/03 职场文书
七一晚会主持词
2015/06/29 职场文书
2019年农民幸福观调查的实践感悟
2019/12/19 职场文书
Mongo服务重启异常问题的处理方法
2021/07/01 MongoDB
带你了解Java中的ForkJoin
2022/04/28 Java/Android
配置nginx负载均衡
2022/05/06 Servers