将Golang和Rust CLI工具移植到D

2020-08-20 16:02:34

几天前,在编程子reddit中,Paulo Henrique Cuchi分享了他用Rust和Go编写命令行工具的经验。正在讨论的工具是他的副项目HashTrack的一个客户。Hashtrace公开了一个GraphQL API,客户端可以使用该API跟踪特定的Twitter标签,并获得相关tweet的实时列表。在这条评论的提示下,我决定编写一个D端口来演示如何使用D来实现类似的目标。我会尽量保持与他在博客帖子中使用的结构相同的结构。

主要原因是,最初的博客文章比较了Go和Rust等静态类型的语言,并恭敬地提到了Nim和Crystal,但没有提到D.属于这一类,所以我认为这将是一个有趣的比较。

我也喜欢D作为一种语言,我在其他各种博客文章中都提到过它。

该手册有一个很长的页面,介绍如何下载和安装参考编译器DMD。Windows用户可以获得安装程序,而MacOS用户可以使用自制软件。在Ubuntu上,我只需添加APT存储库并执行正常的APT安装。有了这个,你将得到DMD,但也会得到包管理器。

我安装Rust是为了了解启动和运行起来有多容易。我惊讶于它是如此容易。我只需要运行交互式安装程序,它会负责其余的工作。我确实必须在路径中添加~/.cargo/bin。考虑到这一点,我可能只需要重新启动控制台就可以使更改生效。

我不费吹灰之力地用Vim编写了hashtrace,但这可能是因为我对标准库中的所有内容都有一定的了解。我确实让文档一直处于打开状态,因为我偶尔会使用没有从正确的包中导入的符号,或者我使用错误的参数调用函数。请注意,就标准库而言,您可以只导入STD,并且可以随心所欲地使用所有东西。不过,对于第三方图书馆来说,您只能靠自己了。

我对工具的状态很好奇,所以我在插件中寻找我最喜欢的IDE,IntelliJ Idea。我找到了这个并安装了它。我还通过克隆DCD和DScanner各自的repos并构建它们,然后配置Idea插件使其指向正确的路径,从而代替了DCD和DScanner。大声向这篇博客文章的作者解释这一过程。

起初我遇到了一些问题,但在更新IDE和插件后,这些问题都得到了修复。我遇到的问题之一是,它无法识别我自己的包裹,并一直将它们标记为可能未定义的。后来我发现,我必须将";module name_of_the_package;";放在文件的顶部才能识别它。

我认为它仍然有一个错误,它不能识别.length,至少在我的机器上是这样。我在Github上开了一期,如果你好奇,可以在这里关注它。

Dub是d中事实上的包管理器,它从https://code.dlang.org/.获取和安装依赖项。对于这个项目,我需要一个HTTP客户端,因为我不想使用cURL。我最终获取了两个依赖项,Requests和它的依赖项cachTools,它本身没有依赖项。不过,由于某些原因,它又获得了12个依赖项:

Rust下载了很多板条箱,但这可能是因为Rust版本的代码比我的版本有更多的功能。例如,它获取了rpassword,这是一个在您将密码字符键入终端时隐藏密码字符的工具,非常类似于Python的getpass函数。这是我的代码中没有的许多东西之一。

由于对GraphQL知之甚少,我不知从何说起。通过在code.dlang.org上搜索GraphQL&34;,我找到了一个相关的图书馆,名字恰如其分地命名为";raphqld&34;。不过,在仔细研究之后,我觉得它更像是一个vibe.d插件,而不是一个真正的客户端,如果真的有这样的事情的话。

在检查了Firefox上的网络请求之后,我意识到对于这个项目,我可以只模拟GraphQL查询和突变,然后用HTTP客户端发送它们。响应只是JSON对象,我可以使用std.json包提供的工具来解析它们。考虑到这一点,我开始寻找HTTP客户端,并确定了请求,一种简单易用的HTTP客户端,但更重要的是,它已经达到了一定的成熟度。

我复制了来自网络检查器的传出请求,并将它们粘贴到单独的.raphql文件中,然后使用适当的变量导入并发送这些文件。大部分功能被放在GraphQLRequest结构中,因为我想向其中注入不同的端点和配置,这是项目的一个要求:

Struct GraphQLRequest{String operationName;String query;JSONValue Variables;Config Configuration;JSONValue toJson(){return JSONValue([";operation Name";:JSONValue(Operation Name),";Variables";:Variables,";Query";:JSONValue(Query),]);}string toString(){return to Json().toPrettyString。Token";,";";)]);return request.post(configuration ation.get(";Endpoint";),toString(),";application/json";);}}。

Struct session{配置配置;void login(String用户名,字符串密码){auto request=createSession(用户名,密码);auto response=request.send();response.throwOnFailure();string Token=response.jsonBody[";data";].object[";createSession";].object[";Token";].str;configuration ation.put(";Token&。CreateSession.graphql";).lineSplitter().join(";\n";);自动变量=SessionPayload(用户名,密码).toJson();Return GraphQLRequest(";CreateSession";,Query,Variables,Configuration);}}struct SessionPayload{String Email;String Password;//TODO:将其设置为模板混合或JSONValue toJson(){return JSONValue([";Email";:JSONValue(Email),&。}string toString(){return to Json().toPrettyString();}}

它是这样的:main()函数从命令行参数创建一个Config结构,并将其注入到会话结构中,该结构实现了login、logout和status命令的功能。CreateSession()方法通过从适当的.raphql文件读取实际请求并传递变量来构造GraphQL请求。我不想用GraphQL突变和查询来污染源代码,所以我将它们移到.raphql文件中,然后在编译过程中借助枚举和导入将其导入。后者需要编译器标志将其指向字符串ImportPath(默认为views/)。

至于login()方法,它只负责发送HTTP请求和处理响应。在这种情况下,它会处理潜在的错误,尽管不是彻底的。然后,它将令牌存储在一个配置文件中,该文件实际上只不过是一个美化的JSON对象。

ThrowOnFailure方法不是请求库核心功能的一部分。它实际上是一个帮助器函数,执行快速而肮脏的错误处理:

Void throwOnFailure(响应响应){if(!response.isSuccessful||";error";in response.jsonBody){string[]error=response.error;抛出新的RequestException(errors.join(";\n";));}}。

因为D支持UFC,所以可以将throwOnFailure(Response)语法重写为response.throwOnFailure()。这使得它可以与其他内置方法(如send())无缝集成。我可能在整个项目中滥用了这个功能。

当涉及到错误处理时,D几乎偏爱异常。这里详细解释了其基本原理。我喜欢它的一点是,除非明确地静默,否则未处理的错误最终会被报告。这就是我能够摆脱简单错误处理的原因。例如,在以下行中:

如果响应正文不包含令牌对象或任何指向它的对象,它将抛出一个异常,该异常将冒泡到主函数,然后在用户面前爆炸。如果我一直在使用Go,我在处理每个阶段的错误时都必须非常小心。老实说,因为每次我调用函数时编写if err!=null都很烦人,所以我很想忽略它。然而,我对围棋的理解是原始的,如果编译器因为你没有对错误返回值做任何事情而对你吠叫,我不会感到惊讶,所以如果我错了,请随时纠正我。

原始帖子中解释的Ruust样式错误处理很有趣。我不认为D在标准库中有类似的东西,但是已经有关于将其作为第三方库实现的讨论。

我只想快速提一下,我没有使用WebSockets来实现watch命令。我尝试使用Vibe.d;的WebSocket客户端,但是我无法使用hashtrace后端,因为它一直在关闭连接。我最终放弃了它,转而支持民调,尽管它令人皱眉。客户端确实可以工作,因为我已经用另一台WebSocket服务器对其进行了测试,所以我可能会在将来再来讨论这一点。

对于CI,我设置了两个构建作业:一个是针对Feature分支的普通构建,另一个是针对Master的发行版,以便以可下载构件的形式提供优化的构建。

然后,我运行了/usr/bin/time-v./hashtrace--list命令来测量内存使用情况,如原始文章中所述。我不知道内存使用情况是否取决于登录用户遵循的标签,但以下是使用dub build-b release构建的D的结果:

最大驻留集大小(千字节):10036最大驻留集大小(千字节):10164最大驻留集大小(千字节):9,940最大驻留集大小(千字节):10060最大驻留集大小(千字节):10008。

还不错。我与我的hashtrace用户运行了Go和Rust版本,得到了以下结果:

最大驻留集大小(千字节):13684最大驻留集大小(千字节):13820最大驻留集大小(千字节):13904最大驻留集大小(千字节):13796最大驻留集大小(千字节):13600。

最大驻留集大小(千字节):9224最大驻留集大小(千字节):9192最大驻留集大小(千字节):9384最大驻留集大小(千字节):9132最大驻留集大小(千字节):9168。

编辑:Redditor skocznymroczny建议我在测试DMD的同时测试LDC和GDC。以下是结果:

最大驻留集大小(千字节):7792最大驻留集大小(千字节):7856最大驻留集大小(千字节):7792最大驻留集大小(千字节):7760最大驻留集大小(千字节):7708。

D有垃圾收集功能,但它也支持智能指针,最近还支持一种受Rust启发的实验性内存管理方法。我不太确定这些功能与标准库集成得有多好,所以我决定让GC为我处理内存。考虑到我在编写代码时没有考虑到内存消耗,我认为结果相当不错。

我认为D是编写这样的命令行工具的可靠语言。我不经常接触外部依赖项,因为标准库拥有我需要的大部分内容。解析命令行参数、处理JSON、单元测试、发出HTTP请求(使用cURL)等都在标准库中可用。如果标准库缺少您需要的东西,第三方软件包就会出现,但我认为在这方面仍有改进的空间。好的一面是,如果你有一种不是在这里发明的心态,或者如果你想作为一名开源贡献者轻松地产生影响,那么你肯定会喜欢D生态系统。