查看原文
其他

比较 Rust 和 Java

2016-09-17 协作翻译 开源中国


#点击上图,立即参与OSC源创会年终盛典#


本文对比了 Rust-1.8.0 nightly 版本和 OpenJDK-1.8.0_60。
从我的其它博客可能很难看得出来我是一个 Java 开发者。坦率地说我很喜欢 Java,它甚至可能让我成为某些少数派的一员。
然而,如果你看到过我发表的其它一些文章,你就会发现我也非常喜欢使用 Rust 编程。所以,我懂 Rust 也懂Java,既然如此,为何不对二者进行一个比较,看看会得到什么样的结论呢?

历史

Java 最初叫 Oak。1995 年之前,在它早期的版本中,Java 只是个别名,直到 1996 年 1 月 1.0 版正式发布。所以到现在 Java 已经超过 20 岁了。Java 最初的(主要)目标是可移植性、简易性和健壮性。Java 一开始被设计成一种面向对象的语言,支持简单的垃圾回收和字节码解释。一开始 Java 运行缓慢而且极耗内存。这些问题现在基本上都解决了。
在早期的互联网时代,Java 被宣传为 “Web 的编程语言”,这都是因为 applet 。但这并没有确定的工作和计划,尽管现今的语言被运行在 web 客户端是开始于 ‘Java’。但是,现今的大多数 Web 架构都被运行在 Java 服务器侧的。
Rust 1.0 长期处于开发(大部分历史可以在 GitHub 上看到,那可以回顾到 2010 年)之中,在 2015 年 5 月被发布。Graydon Hoare 开始这个项目实际上可以追溯到 2008 年左右。自从 2010 年开始, Mozilla 就赞助其开发,随之建立 Servo 项目,旨在创建一个在 Rust 中的现代浏览器引擎。
Rust 开发者开始在设计上进行许多迭代,很多东西被不断扔掉。早期的 Rust 版本就有绿色的线程,如果没记错的话,垃圾收集已经在 0.8 版本期间被移除。而快速迭代曾令很多早期用户不快,但拥有一起公开讨论的文化,使它拥有一个非常全面和周到的设计。
作为一个完全编译的语言,Rust(理论上)不会像JAVA语言一样有着灵活的跨平台性,但Rust基于LLVM,这使得很多(使用Rust的)后端,可以将开销控制在一定范围。此外,由于缺乏一个庞大的运行时环境及其垃圾回收器使得Rust相较JVM而言更加轻量。

生命周期及所有权

另一方面,Rust由于其生命周期及所有权规则能在没有GC(垃圾回收器)的情况下获取对象,其对象通过borrow checker(借还检测器)维护管理,这个borrow checker有时被亲切的称之为borrowck。
这是JAVA所没有的特性,同时为Rust引入了一系列的利与弊。前者因为数据竞争、ConcurrentModificationException及其它祸害JAVA代码相关的东西,编译器会确保其自由性;而后者因为在一些情况下,每个人学习Rust的时候都会运行其head脚本并不用borrow checker,然后问Rust之神为什么他们完美手动添加的生命周期的注解不正确。
事实上,规则很简单:你可以拥有一个素数(你可以做任何事,当借进来时期望丢弃它),一个可变的亦或像许多一成不变的暂借,只要你喜欢。借以相反的顺序结束,这意味着有时候切换开关语句可以借以检查程序。
我喜欢添加那些尽管看起来很巨大的差异,Java的存储模型和Rust是惊人的相似。

类型

Java的基本类型是Rust的基本类型的子集(Java没有无符号整数类型,Rust还提供其它一些类型如一些SIMD类型)。尽管在实践过程中,通过实现良好的逃逸分析等手段,可以减少了实践中指针引用带来的一些痛苦,但是将所有存储对象作为引用,还是会导致指针过多使用。同时因为对象通常存储于堆中(禁止堆外的东西,已经在某些圈子成为时尚),这也解释了为什么Java使用堆比Rust多得多。

Java的整型操作均是封装好的(并没有溢出检查),而Rust的在调试模式进行溢出检查,在发布模式下进行封装不做检查。这使得Rust在测试期间能够检查溢出发现问题,并在发布版本不进行检查提高执行效率。
Rust具有内置的元组类型,可以很容易地基本无额外开销的返回多个值。在Java中,返回一个<A,B>对总是有点麻烦(并附带引用追溯的开销)。在Java中,有人建议增加值类型,从而使其同其它低级语言保持一致,但听说这个建议至少推到Java的10版本或者更后的版本中去考虑。

总而言之,更加彻底的类型系统、借鉴和其它的检查,以及默认的不可修改特性,还有大量蹩脚特性的缺失,这些都意味着在大致相同的时间内写出来的 Rust 代码要不 Java 代码更加的稳定。换言之: 编写 Rust 代码可能要比 Java 更难,但同时写出不正确的 Rust 代码也要比写出不正确的 Java 代码难很多。

Java 有 Class, Rust 有 Trait

Java有class. 我可以说它有类,甚至是很多类。它也有接口,在最新版本里接口甚至可以自定义缺省方法。
我可能不需要重申,Java类里将数据和行为绑定在一起(封装),,控制访问, 继承于其他类, 实现接口等等。Java里的所有东西(除了一些基础数据类型,基本上是空)都是对象 (并且都是某一个类),哪怕它只是一堆静态方法的集合。类相当于类型。大量的基础类构成了Java结构的中心。
相类似,Rust有traits,和Java8的接口诡异的相似。它也有类型(通常是结构体或者枚举)和类型接口实现。它也有固定实现(他们自身类型)。最后访问域通常是模块(模块类似于Java包,但是Java包只是一些类和子包的集合)决定。
这种数据和行为分离的方式在开始会显得有点奇怪,但是实际上它很巧妙, 因为它是的数据类型的复合变得非常自然,并且也可以在已经存在的类型上增加新的traits,这在java里完全不可能。
Rust也有只存活在模块内部的独立函数,这表示在写程序代码的时候可以少些很多套话。比如不用类似public class HelloWorld。
Rust的数据和操作分离的方式候提供了一种基于数据的编程方法,你可以首先创建你的数据结构,然后再围绕它进行构造操作。

模式和SOLID原则

Rust和Java都缺少类似Python的参数命名方式,所以都会使用建造者模式。除此之外,Java好像在应用设计模式方面比Rust更广泛,大概是因为后者还很年轻以至于还没有广泛应用更多的设计模式。由于受Java8 版本的功能的影响,昔日的许多奇葩的设计模式(例如:抽象复合策略工厂的工厂,还有比这个更奇葩的吗?)不再与Java相关。
许多Java开发者拥护SOLID原则,这使得在Java中依旧需要一些设计模式再次重申SOLID原则,单一责任原则,开放封闭原则,里氏替换原则 ,接口分离原则和依赖倒置。
有时,SOLID原则会使应用和类库过度工程化。就像齿轮一样运行,即通过反复调用多层级的对象并且每个对象只负责完成一部分功能特别在企业级应用的代码中,SOLID原则被广泛应用。幸运地是坚持SOLID原则有一个好处是,利于代码重用。
在Rust中,trait一致性(特别是orphan原则)在设计上基本与开放封闭原则相匹配。接口分离原则和依赖倒置能够通过traits实现,但是通常需要泛型,这将造成代码更复杂或生成更多的trait对象,进而增加运行时间。这造成在Rust开发人员不愿意付出上诉代价来坚持接口分离原则和依赖倒置原则。单一责任原则和里氏替换原则在两种语言中都依赖于开发者的觉悟。

控制流


注意这个列表并不完全,及一些并没有完全与所有案例进行比较。大多数Rust的构造器对于不同loop和match声明的不同混合实际上是语法糖(syntactic sugar)。在JAVA中,for-each循环编译为基于迭代的for循环,其结构为for (Iterator<_> i = _; i.hasNext();) { _ v = i.next(); _ }。


所以简而言之,Rust去掉了C语言那样的for循环。因为分配语句没有返回值(a = b = c这样的语句在Rust中不合法),所以if let 和 while let 之类形式的语句要关心这些,这使得这样的语句看起来更加直观, if (x = y) 这样的错误也不大会出现(不过公平的说,大多数JAVA的IDE也会捕捉这些错误)。
Rust的语法糖围绕着异常强大的解构化的match,使得Rust在某些方面比JAVA更高级,但编译器依然设法生成基于语法糖的更紧凑代码,这些代码大多数要感谢LLVM, Rust编译器使用LLVM来生成代码。
错误处理

Java中的异常分为两种类型:被检查异常和未检查异常。前者意味着可能的失效模式,该异常能够被调用者直接处理或沿着调用链(以在你的方法定义中声明抛出异常的方式)向上抛出。后者意味着程序员错误,通常被视为不可恢复。
Rust现在有与线程绑定的“panics”可以被视为运行时异常,它会杀死该线程并应该只能够被另外一个线程捕获.在最近的Rust版本中,使用下面类似:"std::panic::recover(_) that  can call a closure and return a"的语句能够将结果中的任何“panic”转换成"Error"。但是这个功能还不稳定,只能够在每日更新的Rust开发版本中使用。

函数 && 闭包

JAVA终于有了lambdas表达式,但看起来不及Rust的闭包功能强大, Rust的闭包能修改所获环境以与Rust所属规则保持一致。然而,在大部分情况下JAVA还是非常好。函数处理两者是一样的。一个方法的interface(接口)能通过匹配那个方法数据类型的的所有函数自动实现,这挺好的(除了一些小技巧外)。
Rust的函数隐式实现了一些 Fn*() -> _ 类型,因此可以在没有分配栈的情况下,进行不同设置。调用者必须用所给定类型绑定才能正常运行,这通常需要需要一些技巧。然而,某个调用者可以调用比JAVA更加有规则的策略。
JAVA的流提供了低消耗的方式数据并行计算。Rust并没有直接提供支持,但 Rayon 提供了相较低消耗的并行迭代器,然而还有许多其它第三方的库瞄准了并行与并发方面。
JAVA提供可变函数,以内部使用的数组。然而有些小技巧,在某些情况下可以考虑更好的接口。Rust至少可通过宏模拟,或使用切片参数来模拟可变函数,但或许某天Rust也会提供。
JAVA分配函数基于参数类型,内部修改函数名以包括签名,类似如 next()Lllogiq.example.Example 。而Rust并不这样实现:函数永远总是取一系列的参数,尽管泛型可以扩宽可能的签名中的类型集,如 some_func<S: Into<String>>(s: S) 。

元编程
Rust 既有 procedural 又有 procedural。前者是 Rust 程序可以重新令牌树(token trees),而后者是一种 quasiquoting 类型的模板语言。如上描述,类型系统能够被滥用在“有趣”的(例如:破坏他人代码)事情上。
正相反,Java 有少许类库能够在运行时创建字节码,这些类库能够将改变反馈给类加载器。其中一些类库非常好用(Byte Buddy 的作者 Rafael Winterhalter 大声疾呼)考虑到这点,Java 能够在运行时合成代码,令人惊讶的是这种事情不常发生。其次,大量的代码运行良好没有任何字节码的注入。
作为注释,目前阶段 Java 对于程序元数据的支持优于 Rust。这种情况与工具紧密相连,未来我们将完善这部分。时间会告诉我们 Rust 能否在这方面追赶上 Java。
Java 也有运行时反射,该特性又笨重又慢;编译好的字节码总是更快。 在 Rust 中你只需要关注你所需要的,因此你可以使用宏实现任何反射能力。这需要做更多的工作,但是你能掌控任何事情.

其他语言的接口

通过允许定义 extern "C" 函数, Rust 可以有一些接口直接与 C 交互,编译器将承担哪些 C ABI。有一些褶皱涉及到所有权,生命周期和类型,因为本地代码通过定义不能支撑 Rust 的保证,因此,通常还需要一些包装,呈现一个安全和简单的接口。在 Rust 中还有一些允许直接嵌入到 C++ 的子集,但是我既没有时间,也没有必要发现需要测试它。
Rust 明显的好处是层级低,与 C 的接口只需更少的操作。尽管 Java 的设计者真的担心人们会本地化太频繁,但他们的担心是毫无意义的,因为 Java 本身就做得很好。

标准库

Java 的标准库包含大量的东西,从 Annotations 到 ZipOutputStream,甚至更多。几乎只有 Python 能在内置库程度上与其左右, 你只需使用 java.* 和 javax.* (一些 org.* 也被包括在内),就可以做出很多伟大的东西。
Java 不抛弃不放弃。因此,它的 API 已经有三个 UI 工具集(AWT,SWing 和 JavaFX),有Enumeration(枚举) 和  Iterator (迭代器) 接口(它们做的几乎是同样的事情),还有两个 IO 类(java.io 和 java.nio,尽管我承认后者以前者为基础)集合和其他有趣的东西。
Java 的官方 API 倾向于零意外,并且需要极其完整的文档。 由于 Steve Klabnik 被 Mozilla 雇佣为 Rust 的文档专家,也因为他的工作,Rust 的文档也能紧随其后。
Rust 的库是精益和敏锐的。它有一些集合类,包括大量的字符串处理,智能引用和 cell,并支持基本的并发性,还有很多 IO/网络和迷你的 OS 集成。正是因为这样, Rust 的代码将会依赖一些第三方库,不过这些库非常容易获得和管理。这里好的一面是,如果他们只是想要写一个 JSON 解析器,那么就不需要下载一组的 MIDI 类的 - 或者反之亦然。
Rust 的 API 文档允许离线关键词搜索, 这个在你知道自己要寻找是什么的时候很不错。许多的类型通过许多的特性来协调它们自己的行为,这或多或少的阻碍了其可探索性。换句话来讲,一旦你了解了你所要使用的特性,你就能用它们做出令人惊奇的东西来。
尽管同 Java API 的规模相比有点相形见绌,但 Rust 标准库已经具备了令人惊讶的能力。一部分API被标记为不稳定的, 这意味它只会在一些实验性质的编译器或者某些 #![feature(_)] 注解上有用。这样可以使库的开发团队在快速迭代API设计的同时保证版本的稳定。另外,这也限制诸如 BTreeMap 这样的无用特性, 如此会有相当多的方法在发行版本的 Rust 中被去掉。这种情况非常有可能随着时间的推移得到改善。

工具

Java 的工具已经成熟了近10年,因此像预期的一样他非常的出类拔萃。有不计其数的 IDE,构建工具,代码分析工具,部署和操作工具,剖析工具,覆盖测评工具,性能测试框架,文档书写工具,调试工具等等,其中大多数工具都是自由使用的,要么可以公开使用,要么可以私下使用。
Rust 大多数时候还不会产生分歧(尽管一些封装明显的相同),因为 Rust 的工具还没有像 Java 一样成熟。但是,Cargo 已经能够对构建和打包进行完善的管理--我希望在 Java 中也能够使用它。在实际中,我至少能完成编码,但是Rust核心团队将今年定义为IDE年.我们相信我们将看到它成功.

社区和开发者

值得一提的是 Java 社区是庞大的。Java有专业化的氛围,大公司都是用它。它非常好操作,你可以一手编程,一手穿衣服和打领带(我经常这么做)。Java圈子里的人并不意味着没有趣味,相反,我们往往是快乐的一群人,但是一旦谈起业务,我们都会很专注。
Rust的社区与Java相比还很小,但是这里有很多熟练使用 Rust 的人,他们友善,乐于助人,而且幽默,在这里的每一次交流都使我感到快乐, 一点都不烦恼。一些人说他们被迫坚守编码行为准则,但是我还没有看到任何人反对特定的代码,我相信最后的结果会为他们发声的.
在这次比较中 Rust 不被看好,但是就目前来说 Rust 已经做到很好啦。尽管 Rust 的社区规模小,但是 Rust 社区通过敏捷,智慧和专注弥补了人员的不足。在社区成员的支持与帮助下,Rust 成为了一个伟大的语言。

总结

Java 还有很多工作要做,并且我可能在很长一段时间内继续使用它。同时,在可预见的未来,我想我将成为 Rust 圈的一员。Java 和 Rust 都有各自的优点和缺点,它们也都有不错的未来,它们的社区还可以相互学习,当然,这是我个人的观点。


本文为开源中国社区翻译频道翻译文章,由于篇幅较长,本文摘抄其中精简内容,阅读全文请点击“阅读原文”

欢迎任何形式的转载,但请务必注明出处,尊重他人劳动成果。转载时请注明:文章转载自开源中国公众号 (ID:oschina2013)


了解更多详情请点击“阅读原文”



开源中国|ID:oschina2013


每天为你送上精选资讯早点

还有每天的 OSChina 乱弹哦

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存