防锈的第一步

图片


大家好 最近,我遇到了一种新的编程语言Rust。 我注意到他与我以前遇到的其他人不同。 因此,我决定深入研究。 我想分享结果和印象:


  • 我认为,我将从Rust的主要功能入手
  • 我将描述有趣的语法细节
  • 我将解释为什么Rust不太可能占领整个世界

我将立即解释说我已经用Java编写了大约十年的时间,因此我将在钟楼上进行辩论。


杀手级功能


Rust试图在C / C ++等低级语言与Java / C#/ Python / Ruby等高级语言之间居于中间地位...语言与硬件越接近,控制越多,就越容易预测代码的执行方式。 但是,拥有充分的内存访问权限更容易使人发腿。 与C / C ++相反,Python / Java和所有其他出现。 他们不需要考虑清除内存。 最糟糕的是NPE,泄漏并不常见。 但是,要使此工作正常进行,您至少需要一个垃圾收集器,该垃圾收集器进而开始与用户代码并行生活,从而降低其可预测性。 虚拟机仍然提供平台独立性,但是需要多少是一个有争议的问题,我现在不会提出。


Rust是一种低级语言,编译器输出二进制文件,不需要其他技巧即可工作。 用于删除不必要对象的所有逻辑在编译时即已集成到代码中。 在运行时也没有垃圾收集器。 Rust也没有空引用,并且类型是安全的,这使其比Java更加可靠。


内存管理的核心是拥有对象引用和借用的想法。 如果只有一个变量拥有每个对象,那么一旦它在块末尾过期,它所指向的所有内容都可以递归清除。 链接也可以借阅或阅读。 在这里,一位作家和许多读者的原则起作用。


可以在下面的代码段中演示此概念。 从main()方法调用Test () ,该方法创建一个递归数据结构MyStruct ,该结构实现析构函数接口。 Drop允许您设置要在销毁对象之前执行的逻辑。 与Java中的终结器类似,只是与Java不同, drop()方法调用的时刻是确定的。


fn main() { test(); println!("End of main") } fn test() { let a = MyStruct { v: 1, s: Box::new( Some(MyStruct { v: 2, s: Box::new(None), }) ), }; println!("End of test") } struct MyStruct { v: i32, s: Box<Option<MyStruct>>, } impl Drop for MyStruct { fn drop(&mut self) { println!("Cleaning {}", self.v) } } 

结论如下:


 End of test Cleaning 1 Cleaning 2 End of main 

即 在退出测试()之前,以递归方式清除内存。 编译器通过插入必要的代码来解决此问题。 什么是Box and Option,稍后会介绍。


这样,Rust从高级语言获取安全性,并从低级编程语言获取可预测性。


还有什么有趣的


接下来,我认为,该语言的功能将按照重要性从高到低的顺序列出。


哎呀


在这里,Rust通常领先于其他公司。 如果大多数语言得出必须放弃多重继承的结论,那么在Rust中根本就没有继承。 即 一个类只能实现任何数量的接口,而不能从其他类继承。 就Java而言,这意味着将所有类都定型。 通常,用于保持OOP的语法种类不是很多。 也许这是最好的。


为了合并数据,有些结构可能包含实现。 接口称为特征,也可以包含默认实现。 它们没有到达抽象类,因为 不能包含字段;许多人抱怨此限制。 语法如下,我认为这里不需要注释:


 fn main() { MyPrinter { value: 10 }.print(); } trait Printer { fn print(&self); } impl Printer { fn print(&self) { println!("hello!") } } struct MyPrinter { value: i32 } impl Printer for MyPrinter { fn print(&self) { println!("{}", self.value) } } 

在我注意到的功能中,值得注意的是:


  • 类没有构造函数。 只有初始化器通过花括号为字段指定值。 如果需要构造函数,则可以通过静态方法来完成。
  • 实例方法与静态方法的不同之处在于,将&自我引用作为第一个参数。
  • 类,接口和方法也可以被概括。 但是与Java不同,此信息在编译时不会丢失。

更多安全


正如我所说,Rust非常注重代码的可靠性,并在编译阶段尝试防止大多数错误。 为此,排除了使链接为空的能力。 它让我想起了Kotlin中的可为空的类型。 选项用于创建空链接。 就像在Kotlin中一样,当尝试访问这样的变量时,编译器会动手,迫使插入检查。 尝试不检查就将值拉出可能会导致错误。 但这当然不能像Java中那样偶然地完成。


我还喜欢默认情况下所有变量和类字段都是不可变的事实。 您好,科特林。 如果值可能更改,则应使用mut关键字明确指出。 我认为对不变性的渴望极大地提高了代码的可读性和可预测性。 尽管Option由于某种原因是可变的,但我不明白这一点,这是文档中的代码:


 let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2)); 

转账


Rust被称为enum 。 除了有限数量的值外,它们还可以包含任意数据和方法。 因此,它在Java枚举和类之间。 我的第一个示例中的标准枚举Option就是这种类型:


 pub enum Option<T> { None, Some(T), } 

有一个特殊的构造可以处理这些值:


 fn main() { let a = Some(1); match a { None => println!("empty"), Some(v) => println!("{}", v) } } 

也一样


我不打算在Rust上写教科书,而只是想强调它的功能。 在本节中,我将描述还有什么用处,但我认为并不是那么独特:


  • 函数式编程的爱好者不会失望;他们有lambda。 迭代器具有用于处理集合的方法,例如filterfor_each 。 类似于Java流。
  • match构造还可以用于比常规枚举更复杂的事情,例如用于处理模式。
  • 有很多内置类,例如集合: Vec,LinkedList,HashMap等。
  • 您可以创建宏
  • 可以向现有类添加方法
  • 支持自动类型推断
  • 语言附带一个标准的测试框架
  • 内置的cargo实用程序用于构建和管理依赖项

美中不足


这部分是完成图片所必需的。


杀手问题


主要缺点来自主要功能。 你必须付出一切。 在Rust中,使用可变图数据结构非常不方便,因为 任何对象最多只能有一个链接。 要解决此限制,有很多内置类:


  • Box-堆上的不可变值,Java中基元的包装器的类似物
  • 单元格 -变量值
  • RefCell-变量值可通过引用访问
  • RC-引用计数器,用于对一个对象的多个引用

这是不完整的清单。 对于第一个Rust示例,我不顾一切地决定用基本方法编写一个单链表。 最终,到该节点的链接导致以下Option <Rc <RefCell <ListNode> >>


  • 选项 -处理空链接
  • RC-用于多个链接,如 最后一个节点由上一个节点和工作表本身引用
  • RefCell-可变链接
  • ListNode-下一个元素本身

看起来很一般,一个对象周围总共有三个包装器。 将项目简单地添加到列表末尾的代码非常繁琐,并且其中包含一些不明显的内容,例如克隆和借用:


 struct ListNode { val: i32, next: Node, } pub struct LinkedList { root: Node, last: Node, } type Node = Option<Rc<RefCell<ListNode>>>; impl LinkedList { pub fn add(mut self, val: i32) -> LinkedList { let n = Rc::new(RefCell::new(ListNode { val: val, next: None })); if (self.root.is_none()){ self.root = Some(n.clone()); } self.last.map(|v| { v.borrow_mut().next = Some(n.clone()) }); self.last = Some(n); self } ... 

在Kotlin上,这看起来要简单得多:


 public fun add(value: Int) { val newNode = ListNode(null, value); root = root ?: newNode; last?.next = newNode last = newNode; } 

正如我稍后发现的,这种结构对于Rust而言并不常见,我的代码完全是非惯用的。 人们甚至撰写整篇文章:



在此,Rust牺牲了可读性以确保安全性。 此外,此类练习仍可能导致内存中挂起的循环链接,因为 没有垃圾收集器会将它们带走。 我没有用Rust编写工作代码,所以很难说这样的困难使生活复杂化了多少。 收到从业工程师的意见会很有趣。


困难学习


学习Rust的漫长过程很大程度上取决于上一节。 在编写所有内容之前,您必须花时间掌握内存所有权的关键概念,例如 它渗透到每一行。 例如,最简单的清单花了我两个晚上,而在Kotlin上,这件事却在10分钟内写完,尽管这不是我的工作语言。 另外,许多用Rust编写算法或数据结构的熟悉方法看起来会有所不同或根本无法使用。 即 切换到它时,将需要对思维进行更深层次的重组,仅掌握语法是不够的。 这与JavaScript相差甚远,JavaScript可以吞噬并忍受一切。 我认为Rust永远不会是编程学校中孩子要教的语言。 在这种意义上,甚至C / C ++都有更多的机会。


最后


我发现在编译阶段管理内存的想法非常有趣。 在C / C ++中,我没有经验,因此不会与智能指针进行比较。 语法通常令人愉快,并且没有多余的内容。 我批评Rust实施图形数据结构的复杂性,但是我怀疑这是所有非GC编程语言的功能。 也许与科特林的比较并不完全诚实。


待办


在本文中,我根本没有涉及多线程,我认为这是一个单独的大话题。 仍然有计划编写比列表更复杂的数据结构或算法,如果您有想法,请在评论中分享。 知道哪种类型的应用程序通常用Rust编写会很有趣。


已读


如果您对Rust感兴趣,请点击以下链接:



UPD:谢谢大家的评论。 我为自己学到了很多有用的东西。 更正了错误和错别字,添加了链接。 我认为这样的讨论极大地促进了对新技术的研究。

Source: https://habr.com/ru/post/zh-CN433302/


All Articles