当前位置: 首页 > Linux

我想要Go和Rust!

时间:2023-04-06 04:13:59 Linux

大家好,我是张锦涛。最近Rust社区/团队发生了一些变化,因此Rust再次进入了大多数人的视野。最近看到很多朋友说:Rust还值得学吗?社区不稳定?Rust和Go哪个更好?Rust还值得学习吗?如果有人问我这些问题,那么我的回答是:孩子只会做选择,我都要!当然,关于Rust和Go的问题并不新鲜,比如之前的一条推文:这篇文章,我将介绍如何用Go调用Rust。当然,在这篇文章中,我基本不会比较Go和Rust的功能,或者这种方法的性能。只是为了FunFFI和BindingFFI(ForeignFunctionInterface)被翻译为外部函数接口(为了简单起见,下文将使用FFI来指代)。最早的规范来自CommonLisp,写在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选项,使用它内置的库模板。?go-rustgit:(master)?mkdirlib&&cdlib?go-rustgit:(master)?cargonew--librustdemo创建库`rustdemo`包?go-rustgit:(master)?treerustdemorustdemo├──Cargo.toml└──src└──lib.rs1目录,2个文件PrepareRustcodeexterncratelibc;usestd::ffi::{CStr,CString};#[no_mangle]pubextern"C"fnrustdemo(name:*constlibc::c_char)->*constlibc::c_char{letcstr_name=unsafe{CStr::from_ptr(name)};让mutstr_name=cstr_name.to_str().unwrap().to_string();println!("Rust获取输入:\"{}\"",str_name);letr_string:&str="Rustsay:HelloGo";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"然后编译?rustdemogit:(master)?cargobuild--releaseCompilingrustdemov0.1.0(/home/tao/go/src/github.com/tao12345666333/go-rust/lib/rustdemo)在0.22秒内完成发布[优化]目标查看生成的文件,这是一个.so文件(这是因为我是linux环境,如果是其他系统环境就不一样了)?rustdemogit:(master)?lstarget/release/librustdemo.sotarget/release/librustdemo.所以准备Go代码,安装Go环境等就不赘述了,直接在我们的go-rust目录下继续操作即可。写入main.gopackagemain/*#cgoLDFLAGS:-L./lib-lrustdemo#include#include"./lib/rustdemo.h"*/import"C"import("fmt""unsafe")funcmain(){s:="Gosay:HelloRust"input:=C.CString(s)deferC.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-rustgit:(master)?cplib/rustdemo/target/release/librustdemo.solib所以完整的目录结构是:?go-rustgit:(master)?tree-L2..├──go.mod├──lib│├──librustdemo.so│├──rustdemo│└──rustdemo.h└──main.go2目录,5个文件编译:?go-rustgit:(master)?gobuild-ogo-rust-ldflags="-r./lib"main.go?go-rustgit:(master)?./go-rustRustgetInput:"Gosay:HelloRust"Gosay:HelloRustRustsay:HelloGo如您所见,第一行的输出从Go传递到Rust,第二行从Rust发送回Go。满足我们的期望。小结本文介绍了如何使用Go和Rust进行结合,介绍其前置FFI相关知识,然后通过一个小实践演示其完整流程。有兴趣的朋友可以自己练习。欢迎订阅我的文章公众号【MoeLove】