以下为译文:
五年前,我写了一篇文章论述了我当时(至今)最喜欢的脚本语言Ruby让我讨厌的地方(https://blog.yossarian.net/2015/09/28/Five-Things-I-Hate-About-Ruby)。
今天,我将对我目前最喜欢的编译型语言:Rust做同样的事情。
就像最初写的关于Ruby的帖子一样,这些抱怨都是个人观点,反映了我目前对这个语言的最佳理解。就像写关于Ruby的文章一样,这篇也是出于对Rust的热爱而编写的。
闲话少说,让我们开始主题。
字符串地狱
我在开发过程中抱怨Rust中的字符串有两个大方向:
1.字符串类型之间的区别令人困惑
2.字符串类型之间进行转换的方法太多
字符串类型太多
我可以想到5种不同的方式来表示字符串[1],字符串视图或接受字符串形式的方法签名:
&str 用于表示借来的字符串
String用于表示自有的字符串
&OsStr 用于表示从操作系统借用的字符串
OsString 用于表示操作系统中自有的字符串
AsRef用于方法签名,表示一个廉价的&str引用
(我知道最后一个并不是真正的字符串类型,但是它经常出现在惯用的字符串处理代码中。)
作为Rust新手,这几种类型之间的区别非常令人困惑,也使得理解引用变得更加困难(为什么引用&String与&str不同?为什么我不能直接创建str?我从哪里得到&&str?)。
在字符串之间进行转换的方法太多
多种字符串类型和相关特征带来多种转换功能:
&str到String:在不考虑格式化转换或往返一个Vec或[u8]的情况下,至少存在这么多种,String::from(),to_string(),to_owned(),into()
String到&str:as_str(),as_ref(),Deref<Target=str>,&x[..]
适用于OsStr和CStr的类似(可能有损)方法
这些转换中的大多数在性能上是等效的,Rust社区似乎对哪些是“正确的”存在分歧。
我最终习惯于根据上下文使用不同的字符串(例如into(),表示要将a &str转换为a,String以便可以将其返回,to_owned()表示稍后将拥有该字符串的所有权)。
标准库差距
Rust标准库存在一些空白,这些空白使用户空间编程的各个方面都很痛苦:
当前没有获取用户主目录的方法。std::env::home_dir()被明确标记为已弃用,并且该文档鼓励用户使用第三方库dirs(但是该库已经不再维护,详见GitHub链接:https://blog.yossarian.net/2020/05/20/Things-I-hate-about-rust%20/l%20fn:2)[2]。
没有标准的扩展方式~。std::fs::canonicalize支持.和 ..,但不支持~。这其实和上面一点有所重复。
无法通过系统shell调用命令。我知道system[3]命令有各种问题。我也同意它不应该是执行其他进程的默认接口,甚至应该被隔离以防止意外使用。但所有这些都没有改变这样一个事实--即这个命令偶尔会有用,在标准库中实现比最终开发人员直接使用sh-c更可靠。
没有标准的方法进行glob操作。似乎 glob第三方库是执行此操作的半官方方法。
这些都是公认的微小差距,所有这些都可以通过高质量的第三方库解决。但是它们会在开发过程中增加摩擦,鉴于Rust无摩擦的特性,摩擦是特别值得注意的。
特质(Traits)
我喜欢基于特质的组合。但我不喜欢下面这些东西:
被告知我忘记了使用use std::io::Read或者use std::io::Write的方法,但其实原因是我正在调用的方法已经被作用域中的一些东西调用了。我知道为什么 Rust会这样做,不然就会出现编译器警告,但是我仍然感觉很奇怪,尤其是在未使用导入的情况下。
为特质实现特质的语法。impl<T> for Trait for T where T: OtherTrait 虽然不算太糟糕,但它读起来不像impl Trait for OtherTrait那么自然。
有时Rust编译器rustc需要我在静态函数(non-self)特质函数中添加where Self: Sized。我仍然不明白为什么有时需要这样做,而有时则不需要。但我相信这是有正当理由的。
无需扩展的安全索引
给定一个固定数组x = [T; N]和类型为U(U的约束为U::MAX < N)的索引变量i,通过x[i] 索引始终是安全的。尽管如此,Rust编译器rustc希望程序员能够明确将i扩展到usize:
fn main() {
let lookup_table: [u8; 256] = [0_u8; 256];
let index = 5_u8;
println!("{}", lookup_table[index]);
}
失败结果:
error[E0277]: the type `[u8]` cannot be indexed by `u8`
--> src/main.rs:4:20
|
4 | println!("{}", lookup_table[index]);
| ^^^^^^^^^^^^^^^^^^^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<[u8]>` is not implemented for `u8`
= note: required because of the requirements on the impl of `std::ops::Index<u8>` for `[u8]`
虽然这可以理解,但要求程序员要么在索引计划中的任何位置使用as usize(冗长,并掩盖了索引背后为 u8的意图),要么将索引本身变成usize(也掩盖了意图,并使算术运算变得更容易超出界限),还是有些强求。
最后一点:cargo install 有时不能成功
我不知道这是不是一个真正的错误,但是由于我被它坑了几次,所以我将它加了进去。
cargo install显然不知道如何发现带后缀的软件包版本。例如,如果我发布myfakepackage为version 0.0.1-alpha.0,cargo install则会报告如下错误:
$ cargo install myfakepackage
error: could not find `myfakepackage` in registry `https://github.com/rust-lang/crates.io-index`
你必须明确传递—version参数:
$ cargo install myfakepackage --version 0.0.1-alpha.0
总结
我还有其他一些想讨论的内容(对于不支持特质的核心类型的别名,程序包生态系统的样式有点像JS / -y),但是我认为这样做有可能对我非常满意的语言太过消极。
五年过去了,我仍然喜欢Ruby,并且对Rust感到乐观。
备注说明:
1.没包括CString和&CStr,因为它们主要用于FFI上下文,并且可以理解为是不同的。↩
2.我知道在POSIX平台上可靠地获取用户的主目录实际上非常困难。但这并没有改变标准库应该尝试的事实。↩
3.恰当的例子:CLI经常公开钩点和回调,这些钩点和回调能够使用shell语法编写很有用
作者:William Woodruff,研究和工程实践者,开源软件参与者,软件库Homebrew软件开发成员,kbsecret的首席维护者
原文:https://blog.yossarian.net/2020/05/20/Things-I-hate-about-rust
本文为 CSDN 翻译,转载请注明来源出处。
更多精彩推荐
☞重磅!阿里巴巴开源首个边缘计算云原生项目 OpenYurt
![]()
你点的每个“在看”,我都认真当成了喜欢