Go与Rust:编写CLI工具

2020-08-04 08:08:15

本文讲述了我的冒险经历,使用两种我几乎没有经验的语言(两次)编写了一个小型CLI应用程序。

如果您急于直接跳到代码中并亲自进行比较,请查看Go源代码和Rust源代码。

我有一个很受欢迎的项目,叫做HashTrack,这是我为一次技术采访而编写的全栈Web应用程序。这个项目相当小,使用起来也很简单:

面试结束后,我一直在改进这个项目,只是为了好玩,我注意到它可能是一个通过实现CLI工具来测试我技能的完美地方。我已经有了服务器,所以我只需要选择一种语言来在我的项目的API下实现一小部分功能。

Hashtrace login-创建会话令牌,并将其存储在本地文件系统的配置文件中。

在这种情况下,我想要一种我以前几乎没有或没有经验的语言,我还想要一种可以轻松编译为本机可执行文件的语言,这是在CLI工具上拥有的好处。

出于某种原因,我的第一个显而易见的选择是走。但我对铁锈也没什么经验,我觉得它也很适合这个项目。

所以..。为什么不两者兼得呢?因为我在这里的主要目标是学习,这可能是一个很好的机会来实施这个项目两次,并从我的角度找出每一次的利弊。

尊敬的提到克里斯托和尼姆,这也是非常有希望的选择。我期待着在另一个令人喜爱的项目中了解它们。

在使用新工具集时,我首先考虑的是它是否有一种简单的方法可以让我的用户使用它,而无需使用分发包管理器在系统范围内安装它。我们谈论的是版本管理器,他们以用户范围而不是系统范围的方式安装工具,从而使我们的生活变得更容易。Node.js的NVM做得非常好。

使用GO时,有GVM工程处理本地安装和版本管理,安装简单:

我们还需要了解两个环境变量,它们是GOROOT和GOPATH--您可以在这里阅读有关它们的更多信息。

我使用GO发现的第一个问题是,当我弄清楚模块解决方案如何与GOPATH一起工作时,设置一个具有功能性本地开发环境的项目结构变得相当令人沮丧。

最后,我只是在我的项目目录中使用了GOPATH=$(Pwd),主要的好处是有一个针对每个项目的依赖关系设置,比如node_module。它运行得很好。

在完成我的项目后,我发现VIRTALGO存在,并且可以用GOPATH解决我的问题。

Rust有一个名为Rustup的官方项目,该项目管理Rust安装,也称为工具链。使用一行程序可以很容易地设置它。此外,还有一组使用Rustup的可选组件,如RLS和rustfm.许多项目需要Rust工具链的夜间版本,Rustup在版本之间切换没有问题。

对于这两种语言,编辑器工具都是完美无缺的,作为一名VSCode用户,我可以在市场上找到Go和Rust的扩展。

Go没有包管理器,甚至没有官方注册表。相反,它的模块解析的工作方式是您可以从外部URL导入它们。

对于依赖项管理,Rust使用Cargo,它从crates.io下载并编译依赖项,crates.io是Rust包的官方注册中心。板条箱生态系统内的软件包也可以在docs.rs中找到它们的文档。

我的第一个目标是看看在HTTP上实现一个简单的GraphQLquery/突变有多容易。

对于Go语言,我发现了一些库,比如machinebox/GraphQL和shurCoL/GraphQL,第二个库使用strucesstructs来(取消)封送数据,这就是我坚持使用它的原因。

我使用了shurcoL/GraphQL的分支,因为我需要在客户端中设置Authorization头,更改在这个Pull请求中。

Type creationMutation struct{CreateSession struct{Token raphql.String}`raphql:";createSession(email:$email,password:$password)";`}type CreationPayload struct{email string password string}func(client*raphql.Client,payload CreationPayload)(string,error){var突变creationMutation变量:=map[string]interface{}{";email";::raphql.String(payload.Password),}err:=client.Mutate(context.Background(),&;arision,Variables)返回字符串(mumatation.CreateSession.Token),err}。

在Rust中,我必须使用两个库来进行GraphQL调用。这是因为GraphQL_Client与协议无关,它只关注序列化和反序列化数据的代码生成。所以我需要第二个库(Reqwest)来处理HTTP请求。

结构;发布结构{发布令牌:字符串,}发布类型=CREATE_SESSION::Variables;发布异步FN(Context:&;Context,Creation:Creation)->;Result<;Session,api::Error>;{let res=api::Build_BASE_REQUEST(Context).json(&;CreateSession::build_query(creation)).Send()。等着?.json::<;Response<;create_session::ResponseData>;>;()。等待?;匹配res.data{Some(Data)=>;OK(会话{Token:data.create_session.Token,}),_=>;Err(api::Error(api::get_error_message(res).to_string())),}}。

Go和Rust的两个库都没有任何GraphQL viaWebSocket协议的实现。

事实上,Rust的GraphQL_Client支持订阅,但是因为它是协议不可知的,所以我不得不自己实现整个GraphQL WebSocket通信。

要在Go版本中使用WebSockets,应将库修改为支持该协议。因为我已经在用图书馆的叉子了,所以我不想这么做。取而代之的是,我使用了一个穷人观看新推文的方式,即每5秒请求一次API来检索它们,我并不以此为荣。

使用go,有go关键字来产生一个轻量级线程,也称为goroutine。相反,Rust通过调用Thread::Scren使用操作系统线程。除此之外,这两种实现都使用通道在它们的线程之间传输对象。

在GO中,错误与任何其他值一样对待。在围棋中处理错误的常见方法是只检查它们是否存在。

Func(config*Config)(){Contents,Err:=json.MarshalIndent(config,";";,";";,";";)if err!=nil{return err}err=ioutil.WriteFile(config.path,content,0o644)if err!=nil{return err}return nil}。

Ruust具有结果<;T,E>;枚举,它可以封装表示成功的OK(T)或表示错误的Err(E)。它还有<;T>;枚举选项,带一些(T)或不带。如果你熟悉Haskell,你可能会认出这两种语言中的一种和可能的一种。

还有一个错误传播的句法甜点(The?运算符),它从结果或选项结构中解析值,自动返回err(...)。或者当事情变坏的时候什么都不做。

Pub fn(&;mut self)->;io::result<;()>;{let json=serde_json::to_string(&;self.content)?;let mut file=File::create(&;self.path)?;file.write_all(json.as_bytes())}。

Pub fn(&;mut self)->;io::result<;()>;{let json=match serde_json::to_string(&;self.content){OK(Json)=>;json,err(E)=>;return err(E)};let mut file=Match File::Create(&;self.path){OK(File)=>;file,er.。File.write_all(json.as_bytes())}。

以上三个特性的结合构成了我在一门语言中看到的最好的错误处理解决方案,既简单、完善,同时又可维护。

>;time go get hashtrace1,39s user 0,41s system 43%cpu 4,122 total>;time go build-o hashtrace hashtrace#first timego build-o hashtrace hashtrace 0,80s user 0,12s system 152%CPU 0,603 total>;time go build-o hashtrace hashtrace 0,19 s user 0,07。

>;Time Cargo Build编译libc v0.2.67编译cfg-if v0.1.10编译autocfg v1.0.0...。编译hashtrace v0.1.0(/home/Paulo/code/cuchi/hashtrace/cli-rust)已完成开发[未优化+调试信息]目标,共1M 44scargo版本363,80s用户17,05s系统365%cpu 1:44,09。

它编译了所有的依赖项,总共是214个模块。当我们再次运行它时,所有东西都已经编译好了,所以它会立即运行:

>;Time Cargo内部版本#Second Time Finded dev[未优化+调试信息]目标在0.08scargo内部版本0,07s用户0,03s系统104%CPU 0,094总计>;Time Cargo内部版本#进行了更改,编译hashtrace v0.1.0(/home/Paulo/code/cuchi/hashtrace/cli-rust)已完成3.15scargo内部版本3,01s用户0,01s中的开发[未优化+调试信息]目标

如您所见,Rust使用增量编译模型,该模型部分地重新编译模块依赖树,从更改的模块开始,直到它传播到它的依赖项。

如果您正在进行发布构建,则需要更长的时间,这是预期的,因为编译器在内部执行优化任务:

>;Time Cargo Build--发布编译libc v0.2.67编译cfg-if v0.1.10编译autocfg v1.0.0...。编译hashtrace v0.1.0(/home/Paulo/code/cuchi/hashtrace/cli-rust)完成了2M 42scargo版本中的[优化]目标版本--版本1067,72s用户16,95s系统667%CPU 2:42,45。

为了测量内存使用情况,我对每个版本使用了/usr/bin/time-v./hashtrace列表。Time-v显示了很多有趣的信息,但是我们在这里寻找的是进程的最大驻留集大小,也就是执行期间分配的物理内存的峰值。

对于{1..5};中的n,执行/usr/bin/time-v/hashtrace list>;/dev/null 2>;>;time.log donegrep';最大驻留集大小';time.log。

最大驻留集大小(千字节):13632最大驻留集大小(千字节):14016最大驻留集大小(千字节):14244最大驻留集大小(千字节):13648最大驻留集大小(千字节):14500。

最大驻留集大小(千字节):9,840最大驻留集大小(千字节):10068最大驻留集大小(千字节):9,972最大驻留集大小(千字节):10032最大驻留集大小(千字节):10072。

Go有一个垃圾收集器,这是追踪未使用的堆内存并回收它的常用方法,而不是手动执行此操作。由于垃圾收集器是启发式算法的组合,因此总是需要权衡,通常是在性能和内存使用率之间进行权衡。

RUST内存模型具有所有权、借用和生存期等概念,不仅有助于保证内存安全,而且无需手动管理或垃圾回收器即可保证对程序堆内存的完全控制。

它们都是这项工作的非常好的工具。但当然,他们有不同的优先事项。一方面,我们可以选择保持软件开发的简单性、可维护性和可访问性。另一方面,我们有一种专注于稳健性、安全性和性能的语言。

把注意力放在简单上,有时会产生相反的效果(比如GOROOT和GOPATH)。

我仍然不太明白铁锈是如何生活的,如果你试图解决这个问题,它可能会变得相当令人沮丧。

从个人的角度来看,这两个都非常有趣,在C和C++的世界里都是一个很好的补充。它们提供了更广泛的应用程序,如Web服务,甚至前端Web框架,这要归功于WebAssembly:)。

如果您想要对这两种语言进行另一种比这更深入的比较,请从FastThan LIME查看这篇文章。