Python vs. Rust:打破三大障碍

news2024/11/26 5:35:09

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

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

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

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

6ab77cf870237482468ef2087a980861.jpeg

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<'a> {
  part: &'a str,
}  // We annotated this Struct such that its lifetime is linked to part


fn main() {
  let novel = String::from("Do or do not. There is not try.");


  // We split novel on the period but split returns borrows.
  // This means that if novel goes out of scope, so does first_sentence.
  let first_sentence = novel.split('.')
          .next().expect("No period detected!");
   
  // We have annotated the lifetime to be dependent of part.
  // If first_sentence goes out of scope, so does quote.
  let quote = Quote {
    part: first_sentence,
  };
}  // All will be deallocated

2. Rust 不接受 None 为答案

在Python中非常常见的一点在Rust中是不可能的:拥有一个值被设置为 None。这是一个刻意的设计选择,符合Rust的安全性、可预测性和零成本抽象的目标。安全性方面与Rust的所有权、借用和生命周期方面相似:防止引用指向未分配的内存的可能性。通过不给予返回 None 的可能性,将导致更可预测性,因为它强迫开发者明确处理数字可能不存在的情况。由于内存安全和可预测的行为,Rust可以在不牺牲性能的情况下实现其所有高级语言功能。

仅仅拒绝 None 会使 Rust 变得糟糕,因此,创建者提出了一个不错的替代方案:枚举 Option 和 Result。通过这些枚举,我们可以明确表示值的存在或不存在。它还使错误处理变得非常优雅。让我们考虑 Source 5 中使用 Option 的一个示例。

fn divide(x: f64, y: f64) -> Option<f64> {
  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,并执行相应的操作。

9939ed24c3713ec473fbf34184f78915.png

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

fn divide(x: f64, y: f64) -> Result<f64, &'static str> {
  if y == 0.0 {
    Err("Cannot divide by zero!")
  } else {
    Ok(x / y)
  }
}
  
fn main() {
  let result = divide(10.0, 0.0);
  
  match result {
    Ok(value) => println!("Result: {}", value),
    Err(err) => println!("Error: {}", err),
  }
}

Rust的开发者们看到match结构有时可能有点繁琐,因此添加了if let和while let运算符。这些运算符类似于match,但通过一些美味的糖分提供了一些不错的语法糖。甚至还有一个非常酷的?运算符(此处未显示),为美味的糖分添加了一颗樱桃!

let mut values = vec![Some(1), Some(2), None, Some(3)];


while let Some(value) = values.pop() {
  if let Some(inner_value) = value {
    println!("Popped: {}", inner_value);
  } else {
    println!("Found None");
  }
}

使用Python时,我学会了使用Optional关键字为结果类型化,可以是值,也可以是None。但我不得不承认Rust非常巧妙地解决了这一部分。我可以想象Python社区也会朝着这种风格发展,类似于强(更强)类型化的趋势。

3. 类在哪里?

Python和Rust都可以用于两种编程范式:函数式编程(FP)和面向对象编程(OOP)。但是Rust在实现这些所谓的对象的方式上有所不同。在Python中,我们有一个典型的类对象,我们可以将变量和方法与之关联。与许多其他语言(如Java)一样,我们现在可以将这个方法用作基础,并通过创建继承方法和变量的新对象来扩展功能。

在Rust中,没有class关键字,对象与Python基本不同。Rust使用Trait系统进行代码重用和多态性,这可以提供与多重继承相同的好处,但不会出现与多重继承相关的问题。多重继承通常用于将多个类的各种功能组合或共享,但它可能使代码变得复杂和模糊。一个著名的问题是所谓的菱形问题,见Source 8。

class A:
    def method(self):
        print("Method in class A")


class B(A):
    def method(self):
        print("Method in class B")


class C(A):
    def method(self):
        print("Method in class C")
        
class D(B, C):
    pass


obj = D()
obj.method()  # Ambiguity arises here

尽管我认为我们可以轻松地解决这个问题,但如果我要创建一种新语言,我也会尝试以不同的方式解决这个问题。对于多重继承,目标主要是与其他对象共享类似的功能。在Rust中,使用Trait系统更加优雅地实现了这一点。这种方法不仅在Rust中使用,在Scala、Kotlin和Haskell等语言中也有类似的系统。

在Rust中,类是由Enums和Structs创建的。就它们自身而言,它们只是数据结构,但我们可以向这些类添加功能。我们可以直接这样做,然而,通过使用traits,这些功能可以与多个“类”共享。使用traits的一个重要好处是我们可以事先检查某个trait是否已实现。请看以下示例:

// Define a trait for characters that can speak
trait Speaker {
    fn speak(&self);
}


// Implement the Speaker trait for a Jedi
struct Jedi {
    name: String,
}


impl Speaker for Jedi {
    fn speak(&self) {
        println!("{} says: May the Force be with you.", self.name);
    }
}


// Implement the Speaker trait for a Droid
struct Droid {
    model: String,
}


impl Speaker for Droid {
    fn speak(&self) {
        println!("{} says: Beep boop beep.", self.model);
    }
}


// Function that takes any type implementing the Speaker trait
fn introduce(character: &dyn Speaker) {
    character.speak();
}


fn main() {
    let obi_wan = Jedi {
        name: String::from("Obi-Wan Kenobi"),
    };


    let r2d2 = Droid {
        model: String::from("R2-D2"),
    };


    // Call the introduce function with instances of Jedi and Droid
    introduce(&obi_wan);
    introduce(&r2d2);
}

在这个例子中,我们有一个Speaker trait,代表可以说话的角色。我们为两种类型实现了这个trait:Jedi和Droid。每种类型都提供了自己的speak方法的实现。introduce函数接受任何实现Speaker trait的类型,并调用speak方法。在主函数中,我们创建了Jedi(奥比-万·克诺比)和Droid(R2-D2)的实例,并将它们传递给introduce函数,展示了多态性。

对于我这个Pythonista  来说,Rust的trait系统曾经非常令人困惑。花了一些时间我才欣赏到其语法的优雅之处。

总结

Rust是一门非常酷的语言,但绝对不是一门容易学习的语言。Rustlings课程向我展示了一些基础知识,但我远远不熟练到能够承担大型项目的程度。但我真的很喜欢Rust是如何迫使你编写更好、更安全的代码的。

Python仍然是我的日常首选。在工作中,我们的文档流水线完全由Python构建,而且在机器学习领域,我并没有看到一切都转向另一种语言。Python太容易学习了,即使你是一个糟糕的开发者,也能完成工作。

然而,有一些小的动向朝着Rust。当然,一些包如Polars和Pydantic是使用Rust构建的,而HuggingFace也发布了他们自己用Rust构建的第一个版本的名为Candle的机器学习框架。因此,我认为学习一点Rust并不是一个坏主意!

·  END  ·

HAPPY LIFE

92c02cdb9673cb2f5035508eb688d02f.png

本文仅供学习交流使用,如有侵权请联系作者删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1393486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

结构体内存对齐的跨平台做法

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 之前写了一篇文章&#xff1a;使用标准C库读文件时需要注意的一个问题&#xff0c;今天发现是错误的。正确的做法是使用#pragma pack预处理指令。示例程序…

idea社区版 MybatisCodeHelperPro插件使用介绍

文章目录 一、插件介绍二、idea社区版安装MybatisCodeHelperPro插件三、问题记录1. DatabaseHelper插件 加载不了部分数据库链接的列信息2. DatabaseHelper插件 数据库列显示顺序错乱3. MybatisCodeHelperPro插件 数据库字段不提示4. MybatisCodeHelperPro插件 特殊字段增加反引…

【北亚企安数据恢复】RAIDZ多块磁盘离线导致服务器崩溃的数据恢复案例

服务器数据恢复环境&#xff1a; ORACLE SUN ZFS某型号存储&#xff0c;共40块磁盘组建存储池&#xff0c;其中的36块磁盘分为三组&#xff0c;每组12块&#xff0c;单个组使用ZFS特有的RAIDZ管理所有磁盘&#xff0c;RAIDZ级别为2&#xff1b;另外的4块磁盘作为全局热备。存储…

高效解决在本地计算机运行ubuntu服务器端的jupyter lab

文章目录 问题解决方案step1step2step3step4 问题 目前&#xff0c;网上没有什么详细的关于在本地计算机上运行服务器端jupyter lab的教程&#xff0c;由于个人计算机计算资源有限&#xff0c;我们需要利用服务器端的GPU实现高效训练 这篇文章将指导您如何使用 ssh 隧道在远…

Python学习从0到1 day4 python基础语法2 格式化输出和输入方法

其实我不是我&#xff0c;我是青山辽阔 ——24.1.14 一、百分号形式的格式化输出 1.普通输出 #1.定义一些变量 name 陈浩南 age 25 address 广州市天河区#2.变量的输出&#xff08;普通输出&#xff09; print(name) print(age) print(address)#3.Python中&#xff0c;还允…

pycharm import torch

目录 1 安装 2 conda环境配置 3 测试 开始学习Pytorch! 1 安装 我的电脑 Windows 11 Python 3.11 Anaconda3-2023.09-0-Windows-x86_64.exe cuda_11.8.0_522.06_windows.exe pytorch &#xff08;管理员命令行安装&#xff09; pycharm-community-2023.3.2.exe 2 c…

Vim命令大全

文章目录 简述&#xff1a;1. **命令模式&#xff08;Command Mode&#xff09;**2. **插入模式&#xff08;Insert Mode&#xff09;**3. **可视模式&#xff08;Visual Mode&#xff09;**4. **末行模式&#xff08;Ex Mode&#xff09;** 详细使用案例&#xff1a;1. **文件…

Qt 状态机框架:The State Machine Framework (二)

传送门: Qt 状态机框架:The State Machine Framework (一) Qt 状态机框架:The State Machine Framework (二) 1、利用并行态避免态的组合爆炸 假设您想在单个状态机中对汽车的一组互斥属性进行建模。假设我们感兴趣的属性是干净与肮脏&#xff0c;以及移动与不移动。需要四个…

Linux 系统之部署 h5ai 目录列表程序

一、h5ai 介绍 1.1&#xff09;h5ai 简介 h5ai 是用于 HTTP Web 服务器的现代文件索引器&#xff0c;专注于您的文件。目录以吸引人的方式显示&#xff0c;浏览它们通过不同的视图、面包屑和树概述得到增强。最初 h5ai 是 HTML5 Apache Index 的首字母缩写&#xff0c;但现在它…

异步Merkle Tree

1. 引言 前序博客&#xff1a; 利用多核的Rust快速Merkle tree Anoushk Kharangate 2023年论文《Asynchronous Merkle Trees》&#xff0c;其对Merkle tree数据结构进行修改&#xff0c;使得可跨多线程异步计算。 开源代码实现见&#xff1a; https://github.com/anoushk1…

2024华数杯国际数学建模B题思路+代码+模型+论文

2024华数杯国际数学建模B题思路代码模型论文&#xff1a;1.17上午第一时间更新&#xff0c;详细内容见文末名片 问题B&#xff1a;光伏电 背景 中国的电力构成包括传统的能源发电&#xff08;如煤炭、石油和天然气&#xff09;、可再生能源发电 &#xff08;如水力发电、风能…

gin+gorm增删改查目录框架

从网上找资料,发现,很多都是直接的结构 路由&#xff0c;后端的controller层&#xff0c;还有model层&#xff0c;都是放在了同一个main.go文件中&#xff0c;如果写项目的话&#xff0c;还得自己去拆文件&#xff0c;拆代码&#xff0c;经过查询和自己总结&#xff0c;下面放…

ssh免密登录 ssh公钥分发 ssh密钥生成

在连接服务器时&#xff0c;我们会被要求输入用户名对应的密码&#xff0c;如下&#x1f447;&#xff1a; 如果我们要登录的服务器是常用服务器&#xff0c;那么每次登录输入密码就会比较麻烦。那么如何免密登录呢&#xff1f;那就需要使用到rsa公私钥认证了。 生成rsa密钥…

vue 指定区域可拖拽的限定拖拽区域的div(如仅弹窗标题可拖拽的弹窗)

<template><div class"container" ref"container"><div class"drag-box" v-drag><div class"win_head">弹窗标题</div><div class"win_content">弹窗内容</div></div><…

vivado导出时序报告为excel文件的方法

1、打开implementation下的report timing summary 2、选择要看的时钟右键点击report_timing 3、在新打开的timing窗口中&#xff0c;选择setup或者hold&#xff0c;选中一条路径右键&#xff0c;点击export to spreadsheet&#xff0c;此时就可以存为table.xlsx文件

【MySQL】权限控制

DCL-权限控制 查询权限 show grants for 用户名主机名;授予权限 grant 权限列表 on 数据库名.表名 to 用户名主机名;grant all on test.* to user%; %是通配符&#xff0c;表示任意主机。撤销权限 revoke 权限列表 on 数据库名.表名 from 用户名主机名;revoke all on test.*…

旅游平台day02

1. 用户注册 概述&#xff1a; 常见的注册方式&#xff1a;邮箱注册、手机号注册、昵称注册、或者以上几种同时支持 本项目仅仅支持手机号注册 需求&#xff1a; 项目启动后&#xff0c;访问regist.html进入注册页面 手机号校验 前后台都需要对手机号进行校验 前端校验&am…

HashMap学习和线程安全的HashMap

HashMap的底层数据结构&#xff1f; HashMap在JDK1.8里面的Node数组加链表加红黑树&#xff0c;当链表长度大于8且数组长度大于64&#xff0c;链表转化为红黑树。当红黑树节点数小于6&#xff0c;红黑树转化为链表。在JDK1.7中是数组加链表。 为什么要用红黑树&#xff1f; 当…

react 第一个项目

sudo npx create-react-app reactdemo01 npx node.js工具 create-react-app 核心包&#xff08;固定写法&#xff09;用于创建react项目 后跟项目名层 启动一个新的 React 项目 – React 中文文档 //项目的根组件 //App -> index.js ->/Users/king/Documents/react…

芯片新闻-Global Semiconductor Sales Increase 5.3% Year-to-Year in November

11 月标志着一年多以来市场同比增长的第一个月&#xff1b;全球芯片销量环比增长2.9% 华盛顿——一月。 2024 年 12 月 9 日——半导体行业协会 (SIA) 今天宣布&#xff0c;2023 年 11 月全球半导体行业销售额总计 480 亿美元&#xff0c;比 2022 年 11 月的 456 亿美元总额增…