Python vs. Rust:打破三大障碍

2024年 5月 21日 63.7k 0

在我周围的每个人都知道我是Python 的忠实粉丝。大约15年前,当我对 Mathworks Matlab 感到厌倦时,我开始使用Python。虽然Matlab的理念看起来不错,但在掌握了Python之后,我再也没有回头。我甚至成为了我所在大学的Python传道者,"传播这个词"。

会编码并不等于成为软件开发者。当我了解到强类型、SOLID原则和通用编程架构等主题时,我也瞥见了其他编程语言以及它们如何解决问题。特别是Rust引起了我的兴趣,因为我经常看到基于Rust的Python包(例如Polars)。

为了对Rust有一个合适的介绍,我参加了官方的Rustlings课程,这是一个包含96个小型编码问题的本地Git存储库。尽管这是相当可行的,但Rust与Python非常不同。Rust编译器是一个非常严格的家伙,不接受"也许"这个答案。以下是我认为Rust和Python之间的三个主要区别。

免责声明:虽然我对Python相当熟练,但我对其他语言了解有点生疏。我仍在学习Rust,可能对某些部分有误解。

Python vs. Rust:打破三大障碍-1

1. 所有权、借用和生命周期

所有权和借用可能是Rust编程语言最基本的方面。它旨在确保内存安全,而无需所谓的垃圾收集器。这是Rust的一个独特概念,我尚未在其他语言中看到过。让我们以一个例子开始,我们将值42分配给变量answer_of_life。Rust现在将在内存中分配一些空间(这有点复杂,但现在我们简化一下),并将"所有权"附加到这个变量上。重要的是要知道一次只能有一个所有者。一些操作会"转移所有权",使先前的变量引用无效。这通过防止诸如双重释放内存、数据竞争和悬空引用等问题来确保内存安全。

fn main() {
  let s1 = String::from("Hello, Rust!");
  
  // Ownership of the String is transferred from s1 to s2
  let s2 = s1;
  
  // This results in a compilation
  println!("s1: {}", s1);
} // s2 goes out of scope and memory is freed

一个在其他语言中也使用的术语是作用域。这可以被看作是代码中的一个"生存区"。每当代码离开一个作用域时,所有具有所有权的变量都将被释放。这在Python中是根本不同的事情。Python使用垃圾收集器,在没有对其的引用时释放变量。在Source 1的例子中,将所有权从变量s1转移到s2,此后变量s1将无法使用。

对于Python用户来说,所有权可能会令人困惑,因为在开始阶段确实是一场真正的斗争。在Source 1的例子中有点过于简单了。Rust强制你考虑一个变量是在哪里创建的以及它应该如何被转移。例如,当你将参数传递给函数时,所有权可以如Source 2中所示被转移。

fn take_ownership(some_string: String) {
  // The ownership of the String is transferred to some_string
  println!("Got ownership: {}", some_string);
}  // some_string goes out of scope and the memory is freed

fn main() {
  let my_string = String::from("Hello, ownership!");

  // Ownership is transferred to the function and my_string is
  // no longer valid
  take_ownership(my_string);

  // This results in a compilation error as my_string is no
  // longer the owner of the String.
  println!("my_string: {}", my_string);
} // my_string is no longer valid here, as it was moved to take_ownership

仅仅转移所有权可能很麻烦,对于某些用例甚至可能行不通,因此Rust提出了所谓的借用系统。与转移所有权不同,变量同意借用该变量,而原始变量仍保持所有权。默认情况下,借用变量是不可变的,即只读的,但通过添加mut关键字,借用甚至可以是可变的。在Source 3中,我展示了两个不可变的借用和一个可变的借用的例子。当函数超出范围时,所有变量都将被删除。

fn main() {
  // s is the owner of the mutable String
  let mut s = String::from("Hello, Rust!");

  let r1 = &s;  // Immutable borrow
  let r2 = &s;  // Another immutable borrow

  println!("r1: {}, r2: {}", r1, r2);

  let r3 = &mut s;  // Mutable borrow
  r3.push_str(", and Pythonista!"); // Modifying the borrowed value

  println!("r3: {}", r3);
} // r1, r2, r3, and s go out of scope and memory is automagically freed

生命周期是Rust中与借用和所有权相关的一个概念,它帮助编译器强制规定引用可以有效存在多长时间的规则。你可能会遇到这样一种情况,你创建了一个结构或一个函数,它是使用两个借用构建的。这意味着现在函数或结构的结果可能取决于先前的输入。为了更明确地表示这一点,我们可以通过注释生命周期来表达关系。在Source 4中查看一个例子。

struct Quote Option {
  if y == 0.0 {
    None
  } else {
    Some(x / y)
  }
}
  
fn main() {
  let result = divide(10.0, 2.0);
  
  match result {
    Some(value) => println!("Result: {}", value),
    None => println!("Cannot divide by zero!"),
  }
}

等一下!你不是说没有 None 吗?这也是我第一次被欺骗的地方,但在这里,None 是一个不带参数的特殊枚举结构。同样,Some 也是一个特殊的结构,但它可以带一个参数。我们的 divide() 函数返回这些可能的枚举值之一,我们稍后可以检查它是什么并采取相应的操作。

没有 None 并强制返回值使得 Rust 变得非常可预测。

主函数使用 match 结构进行结果处理,这非常方便。这在某种程度上类似于其他语言中的 switch/case 构造,除了 Python(见图2中Guido的回应)。match 检查是枚举 Some 还是枚举 None,并执行相应的操作。

Python vs. Rust:打破三大障碍-2

Option 枚举是用于可以返回值或不返回值的函数的特殊结构。对于可以返回值或错误的函数,Rust 还有一个更明确的枚举,称为 Result。思想完全相同,主要区别在于 Option 有一个默认的“错误”值 None,而 Result 需要一个显式的“错误”类型。在 Source 6 中,divide 函数使用 Result 重写。

fn divide(x: f64, y: f64) -> Result

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论